[release] Snap to 06c59d58e7
Change-Id: I406ffade91cfa1b0ba0f0a2b9391036d3b761bb7
diff --git a/BUILD.gn b/BUILD.gn
index c1f6b09..e02ed53 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -16,24 +16,13 @@
 group("tests") {
   testonly = true
 
-  deps = [
-    ":dart_target_tests",
-    ":dart_unittests",
-    "session_shells/ermine/session:tests",
-  ]
-}
-
-# Dart tests which can run on target
-group("dart_target_tests") {
-  testonly = true
-  deps = [ "bin:dart_target_tests" ]
+  deps = [ ":dart_unittests" ]
 }
 
 # Dart unit tests which can run on the host or target
 group("dart_unittests") {
   testonly = true
   deps = [
-    "bin:dart_unittests",
     "examples:dart_unittests",
     "lib:dart_unittests",
     "session_shells:dart_unittests",
diff --git a/bin/BUILD.gn b/bin/BUILD.gn
index 50f0002..fba027a 100644
--- a/bin/BUILD.gn
+++ b/bin/BUILD.gn
@@ -3,27 +3,5 @@
 # found in the LICENSE file.
 
 group("bin") {
-  deps = [
-    "settings:settings",
-    "simple_browser:simple-browser",
-  ]
-}
-
-group("dart_target_tests") {
-  testonly = true
-
-  deps = [ "simple_browser/target_test:simple-browser-target-test" ]
-}
-
-group("dart_unittests") {
-  testonly = true
-
-  # TODO(fxb/41505): Temporarily disable flutter_tester tests on mac hosts.
-  _flutter_tester_tests = []
-  if (host_os != "mac") {
-    _flutter_tester_tests +=
-        [ "simple_browser:simple_browser_unittests($host_toolchain)" ]
-  }
-
-  deps = _flutter_tester_tests
+  deps = [ "settings:settings" ]
 }
diff --git a/bin/simple_browser/BUILD.gn b/bin/simple_browser/BUILD.gn
deleted file mode 100644
index 47a1f9b..0000000
--- a/bin/simple_browser/BUILD.gn
+++ /dev/null
@@ -1,123 +0,0 @@
-# Copyright 2019 The Fuchsia Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import("//build/components.gni")
-import("//build/dart/dart_library.gni")
-import("//build/fidl/fidl.gni")
-import("//build/flutter/flutter_component.gni")
-import("//build/flutter/test.gni")
-import("//build/testing/environments.gni")
-import("//build/testing/flutter_driver.gni")
-
-dart_library("lib") {
-  package_name = "simple_browser"
-  null_safe = true
-
-  sources = [
-    "app.dart",
-    "create_web_context.dart",
-    "main.dart",
-    "src/blocs/tabs_bloc.dart",
-    "src/blocs/webpage_bloc.dart",
-    "src/models/app_model.dart",
-    "src/models/tabs_action.dart",
-    "src/models/webpage_action.dart",
-    "src/services/simple_browser_navigation_event_listener.dart",
-    "src/services/simple_browser_web_service.dart",
-    "src/utils/browser_shortcuts.dart",
-    "src/utils/sanitize_url.dart",
-    "src/utils/tld_checker.dart",
-    "src/utils/tlds_provider.dart",
-    "src/utils/valid_tlds.dart",
-    "src/widgets/error_page.dart",
-    "src/widgets/history_buttons.dart",
-    "src/widgets/navigation_bar.dart",
-    "src/widgets/navigation_field.dart",
-    "src/widgets/tabs_widget.dart",
-    "test_main.dart",
-  ]
-
-  deps = [
-    "//sdk/dart/fidl",
-    "//sdk/dart/fuchsia_internationalization_flutter",
-    "//sdk/dart/fuchsia_logger",
-    "//sdk/dart/fuchsia_scenic",
-    "//sdk/dart/fuchsia_scenic_flutter",
-    "//sdk/dart/fuchsia_services",
-    "//sdk/dart/zircon",
-    "//sdk/fidl/fuchsia.intl",
-    "//sdk/fidl/fuchsia.io",
-    "//sdk/fidl/fuchsia.ui.shortcut",
-    "//sdk/fidl/fuchsia.ui.views",
-    "//sdk/fidl/fuchsia.web",
-    "//src/experiences/bin/simple_browser_internationalization:internationalization",
-    "//src/experiences/session_shells/ermine/keyboard_shortcuts",
-    "//third_party/dart-pkg/git/flutter/packages/flutter",
-    "//third_party/dart-pkg/git/flutter/packages/flutter_driver",
-    "//third_party/dart-pkg/git/flutter/packages/flutter_localizations",
-    "//third_party/dart-pkg/git/flutter/packages/flutter_test",
-    "//third_party/dart-pkg/pub/html_unescape",
-    "//third_party/dart-pkg/pub/http",
-    "//third_party/dart-pkg/pub/intl",
-    "//third_party/dart-pkg/pub/meta",
-  ]
-}
-
-resource("keyboard-shortcuts") {
-  sources = [ "config/keyboard_shortcuts.json" ]
-  outputs = [ "data/keyboard_shortcuts.json" ]
-}
-
-resource("material-icons") {
-  sources = [ "//prebuilt/third_party/dart/${host_platform}/bin/resources/devtools/assets/fonts/MaterialIcons-Regular.otf" ]
-  outputs = [ "data/MaterialIcons-Regular.otf" ]
-}
-
-flutter_component("component") {
-  if (flutter_driver_enabled) {
-    main_dart = "test_main.dart"
-  } else {
-    main_dart = "main.dart"
-  }
-  component_name = "simple-browser"
-  manifest = "meta/simple_browser.cmx"
-  deps = [
-    ":keyboard-shortcuts",
-    ":lib",
-    ":material-icons",
-  ]
-}
-
-fuchsia_package("simple-browser") {
-  deps = [ ":component" ]
-}
-
-# fx test simple_browser_unittests
-flutter_test("simple_browser_unittests") {
-  null_safe = true
-  sources = [
-    "browser_shortcuts_test.dart",
-    "sanitize_url_test.dart",
-    "simple_browser_test.dart",
-    "tabs_bloc_test.dart",
-    "tld_checker_test.dart",
-    "webpage_bloc_test.dart",
-    "widgets/error_page_test.dart",
-    "widgets/history_buttons_test.dart",
-    "widgets/navigation_bar_test.dart",
-    "widgets/navigation_field_test.dart",
-    "widgets/tabs_widget_test.dart",
-  ]
-
-  deps = [
-    ":lib",
-    "//sdk/dart/fuchsia_logger",
-    "//sdk/fidl/fuchsia.ui.shortcut",
-    "//sdk/fidl/fuchsia.web",
-    "//third_party/dart-pkg/git/flutter/packages/flutter",
-    "//third_party/dart-pkg/git/flutter/packages/flutter_test",
-    "//third_party/dart-pkg/pub/mockito",
-    "//third_party/dart-pkg/pub/test",
-  ]
-}
diff --git a/bin/simple_browser/OWNERS b/bin/simple_browser/OWNERS
deleted file mode 100644
index 9de9b92..0000000
--- a/bin/simple_browser/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-yhlee@google.com
diff --git a/bin/simple_browser/README.md b/bin/simple_browser/README.md
deleted file mode 100644
index a8d7b11..0000000
--- a/bin/simple_browser/README.md
+++ /dev/null
@@ -1,12 +0,0 @@
-# Browser
-
-Minimal traditional web browser with navigation and a text field for
-entering the URL.
-
-## Testing
-
-To run unit tests
-```
-fx set workstation.<BOARD> --with //src/experiences:dart_unittests
-fx run-host-tests simple_browser_unittests
-```
diff --git a/bin/simple_browser/config/keyboard_shortcuts.json b/bin/simple_browser/config/keyboard_shortcuts.json
deleted file mode 100644
index 7809396..0000000
--- a/bin/simple_browser/config/keyboard_shortcuts.json
+++ /dev/null
@@ -1,67 +0,0 @@
-{
-    "closeTab": [
-        {
-            "char": "w",
-            "chord": "Control + w",
-            "description": "Close the current tab",
-            "modifier": "control"
-        }
-    ],
-    "focusField": [
-        {
-            "char": "l",
-            "chord": "Control + l",
-            "description": "Place the focus on the Address Bar",
-            "modifier": "control",
-            "trigger": "pressAndRelease"
-        }
-    ],
-    "goBack": [
-        {
-            "char": "left",
-            "chord": "Alt + <-",
-            "description": "Open the previous page from your browsing history in the current tab",
-            "modifier": "alt"
-        }
-    ],
-    "goForward": [
-        {
-            "char": "right",
-            "chord": "Alt + ->",
-            "description": "Open the next page from your browsing history in the current tab",
-            "modifier": "alt"
-        }
-    ],
-    "newTab": [
-        {
-            "char": "t",
-            "chord": "Control + t",
-            "description": "Open a new tab",
-            "modifier": "control"
-        }
-    ],
-    "nextTab": [
-        {
-            "char": "tab",
-            "chord": "Control + tab",
-            "description": "Jump to the next open tab",
-            "modifier": "control"
-        }
-    ],
-    "previousTab": [
-        {
-            "char": "tab",
-            "chord": "Control + Shift + tab",
-            "description": "Jump to the previous open tab",
-            "modifier": "control + shift"
-        }
-    ],
-    "refresh": [
-        {
-            "char": "r",
-            "chord": "Control + r",
-            "description": "Reload the current page",
-            "modifier": "control"
-        }
-    ]
-}
diff --git a/bin/simple_browser/lib/app.dart b/bin/simple_browser/lib/app.dart
deleted file mode 100644
index e111840..0000000
--- a/bin/simple_browser/lib/app.dart
+++ /dev/null
@@ -1,137 +0,0 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'dart:ui';
-
-import 'package:flutter/material.dart';
-import 'package:flutter_localizations/flutter_localizations.dart';
-import 'package:fuchsia_scenic_flutter/fuchsia_view.dart' show FuchsiaView;
-import 'package:internationalization/localizations_delegate.dart'
-    as localizations;
-import 'package:internationalization/strings.dart';
-import 'package:internationalization/supported_locales.dart'
-    as supported_locales;
-import 'package:intl/intl.dart';
-import 'src/blocs/webpage_bloc.dart';
-import 'src/models/app_model.dart';
-import 'src/widgets/error_page.dart';
-import 'src/widgets/navigation_bar.dart';
-import 'src/widgets/tabs_widget.dart';
-
-// TODO(fxb/45264): Replace these with colors from the central Ermine styles.
-const _kErmineColor100 = Color(0xFFE5E5E5);
-const _kErmineColor200 = Color(0xFFBDBDBD);
-const _kErmineColor300 = Color(0xFF282828);
-const _kErmineColor400 = Color(0xFF0C0C0C);
-
-const _kTextStyle = TextStyle(color: _kErmineColor400, fontSize: 14.0);
-
-class App extends StatelessWidget {
-  final AppModel model;
-
-  const App(this.model);
-
-  @override
-  Widget build(BuildContext context) {
-    return FutureBuilder(
-      future: model.localeStream.first,
-      builder: (context, snapshot) => snapshot.hasData
-          ? StreamBuilder<Locale>(
-              stream: model.localeStream,
-              initialData: snapshot.data as Locale,
-              builder: (context, snapshot) {
-                final locale = snapshot.data;
-                Intl.defaultLocale = locale.toString();
-                return MaterialApp(
-                  title: Strings.browser,
-                  theme: ThemeData(
-                    colorScheme: ColorScheme.light(
-                      background: _kErmineColor100,
-                      onBackground: _kErmineColor400,
-                      primary: _kErmineColor100,
-                      primaryVariant: _kErmineColor200,
-                      onPrimary: _kErmineColor400,
-                      secondary: _kErmineColor400,
-                      secondaryVariant: _kErmineColor300,
-                      onSecondary: _kErmineColor100,
-                      surface: _kErmineColor100,
-                      onSurface: _kErmineColor400,
-                    ),
-                    fontFamily: 'RobotoMono',
-                    textSelectionTheme: TextSelectionThemeData(
-                      selectionColor: _kErmineColor200,
-                      cursorColor: _kErmineColor400,
-                      selectionHandleColor: _kErmineColor400,
-                    ),
-                    textTheme: TextTheme(
-                      bodyText2: _kTextStyle,
-                      subtitle1: _kTextStyle,
-                    ),
-                  ),
-                  locale: locale,
-                  localizationsDelegates: [
-                    localizations.delegate(),
-                    GlobalMaterialLocalizations.delegate,
-                    GlobalWidgetsLocalizations.delegate,
-                  ],
-                  supportedLocales: supported_locales.locales,
-                  scrollBehavior: MaterialScrollBehavior().copyWith(
-                    dragDevices: {
-                      PointerDeviceKind.mouse,
-                      PointerDeviceKind.touch
-                    },
-                  ),
-                  home: Scaffold(
-                    body: Column(
-                      children: <Widget>[
-                        AnimatedBuilder(
-                          animation: model.tabsBloc.currentTabNotifier,
-                          builder: (_, __) => BrowserNavigationBar(
-                            bloc: model.tabsBloc.currentTab,
-                            newTab: model.newTab,
-                            fieldFocus: model.fieldFocus,
-                          ),
-                        ),
-                        TabsWidget(bloc: model.tabsBloc),
-                        Expanded(child: _buildContent()),
-                      ],
-                    ),
-                  ),
-                );
-              },
-            )
-          : Offstage(),
-    );
-  }
-
-  Widget _buildContent() => AnimatedBuilder(
-        animation: model.tabsBloc.currentTabNotifier,
-        builder: (_, __) => model.tabsBloc.currentTab == null
-            // hide if no tab selected
-            ? _buildEmptyPage()
-            : AnimatedBuilder(
-                animation: model.tabsBloc.currentTab!.pageTypeNotifier,
-                builder: (_, __) =>
-                    _buildPage(model.tabsBloc.currentTab!.pageType),
-              ),
-      );
-
-  Widget _buildPage(PageType pageType) {
-    switch (pageType) {
-      case PageType.normal:
-        return _buildNormalPage();
-      case PageType.error:
-        // show error for error state
-        return _buildErrorPage();
-      default:
-        // hide if no content in tab
-        return _buildEmptyPage();
-    }
-  }
-
-  Widget _buildNormalPage() =>
-      FuchsiaView(controller: model.tabsBloc.currentTab!.fuchsiaViewConnection);
-  Widget _buildErrorPage() => ErrorPage();
-  Widget _buildEmptyPage() => Container();
-}
diff --git a/bin/simple_browser/lib/create_web_context.dart b/bin/simple_browser/lib/create_web_context.dart
deleted file mode 100644
index 2154f9d..0000000
--- a/bin/simple_browser/lib/create_web_context.dart
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'package:fidl/fidl.dart' show InterfaceHandle;
-import 'package:fidl_fuchsia_io/fidl_async.dart' as fidl_io;
-import 'package:fidl_fuchsia_web/fidl_async.dart' as web;
-import 'package:fuchsia_services/services.dart' show Incoming;
-import 'package:zircon/zircon.dart';
-
-/// Creates a web context for creating new web frames
-web.ContextProxy createWebContext() {
-  final context = web.ContextProxy();
-  final contextProvider = web.ContextProviderProxy();
-  final contextProviderProxyRequest = contextProvider.ctrl.request();
-  Incoming.fromSvcPath()
-    ..connectToServiceByNameWithChannel(contextProvider.ctrl.$serviceName,
-        contextProviderProxyRequest.passChannel())
-    ..close();
-  final channel = Channel.fromFile('/svc');
-  final webFeatures = web.ContextFeatureFlags.network |
-      web.ContextFeatureFlags.audio |
-      web.ContextFeatureFlags.hardwareVideoDecoder |
-      web.ContextFeatureFlags.keyboard |
-      web.ContextFeatureFlags.vulkan;
-  final web.CreateContextParams params = web.CreateContextParams(
-      serviceDirectory: InterfaceHandle<fidl_io.Directory>(channel),
-      features: webFeatures);
-  contextProvider.create(params, context.ctrl.request());
-  contextProvider.ctrl.close();
-
-  return context;
-}
diff --git a/bin/simple_browser/lib/main.dart b/bin/simple_browser/lib/main.dart
deleted file mode 100644
index b48f479..0000000
--- a/bin/simple_browser/lib/main.dart
+++ /dev/null
@@ -1,91 +0,0 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// ignore_for_file: unused_import
-import 'dart:io';
-
-import 'package:flutter/material.dart';
-import 'package:flutter/services.dart';
-import 'package:flutter_test/flutter_test.dart';
-import 'package:fuchsia_logger/logger.dart';
-import 'package:fuchsia_scenic_flutter/fuchsia_view.dart';
-import 'package:fuchsia_services/services.dart';
-
-import 'app.dart';
-import 'create_web_context.dart';
-import 'src/blocs/tabs_bloc.dart';
-import 'src/blocs/webpage_bloc.dart';
-import 'src/models/app_model.dart';
-import 'src/models/tabs_action.dart';
-import 'src/models/webpage_action.dart';
-import 'src/services/simple_browser_web_service.dart';
-import 'src/utils/tld_checker.dart';
-
-final _handler = MethodChannel('flutter_driver/handler');
-
-void main() {
-  setupLogger(name: 'simple_browser');
-  final _context = createWebContext();
-  TldChecker().prefetchTlds();
-  ComponentContext.createAndServe();
-
-  // Loads MaterialIcons-Regular.otf
-  File file = File('/pkg/data/MaterialIcons-Regular.otf');
-  if (file.existsSync()) {
-    FontLoader('MaterialIcons')
-      ..addFont(() async {
-        return file.readAsBytesSync().buffer.asByteData();
-      }())
-      ..load();
-  }
-
-  // Binds |tabsBloc| here so that it can be referenced in the TabsBloc
-  // constructor arguments.
-  late TabsBloc tabsBloc;
-
-  tabsBloc = TabsBloc(
-    tabFactory: () {
-      SimpleBrowserWebService webService = SimpleBrowserWebService(
-        context: _context,
-        popupHandler: (tab) => tabsBloc.request.add(
-          AddTabAction(tab: tab),
-        ),
-        onLoaded: () => tabsBloc.currentTab!.request.add(SetFocusAction()),
-      );
-
-      // Enables the web console log only for debug.
-      assert(() {
-        webService.enableConsoleLog();
-        return true;
-      }());
-
-      return WebPageBloc(
-        webService: webService,
-      );
-    },
-    disposeTab: (tab) {
-      tab.dispose();
-    },
-  );
-
-  tabsBloc.request.add(NewTabAction());
-
-  final appModel = AppModel.fromStartupContext(
-    tabsBloc: tabsBloc,
-  );
-
-  runApp(App(appModel));
-
-  // Moves focus to the webview whenever the browser gets focused.
-  FocusState.instance.stream().listen(appModel.onFocus);
-
-  // This call is used only when flutter driver is enabled.
-  if (TestDefaultBinaryMessengerBinding.instance != null) {
-    _handler.setMockMethodCallHandler((call) async {
-      final url = call.method;
-      tabsBloc.currentTab!.request.add(NavigateToAction(url: url));
-      log.info('Navigate to $url...');
-    });
-  }
-}
diff --git a/bin/simple_browser/lib/src/blocs/tabs_bloc.dart b/bin/simple_browser/lib/src/blocs/tabs_bloc.dart
deleted file mode 100644
index 7768780..0000000
--- a/bin/simple_browser/lib/src/blocs/tabs_bloc.dart
+++ /dev/null
@@ -1,132 +0,0 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'dart:async';
-import 'dart:collection';
-import 'package:flutter/foundation.dart';
-import '../blocs/webpage_bloc.dart';
-import '../models/tabs_action.dart';
-
-// Business logic for browser tabs.
-// Sinks:
-//   TabsAction: a tabs action - open new tab, focus tab, etc.
-// Value Notifiers:
-//   Tabs: the list of open tabs.
-//   CurrentTab: the currently focused tab.
-class TabsBloc {
-  final _tabsList = <WebPageBloc>[];
-  final WebPageBloc Function() tabFactory;
-  final void Function(WebPageBloc tab) disposeTab;
-
-  // Value Notifiers
-  final ValueNotifier<UnmodifiableListView<WebPageBloc>> _tabs =
-      ValueNotifier<UnmodifiableListView<WebPageBloc>>(
-          UnmodifiableListView(<WebPageBloc>[]));
-  final ValueNotifier<WebPageBloc?> _currentTab =
-      ValueNotifier<WebPageBloc?>(null);
-
-  ChangeNotifier get tabsNotifier => _tabs;
-  ChangeNotifier get currentTabNotifier => _currentTab;
-
-  UnmodifiableListView<WebPageBloc> get tabs => _tabs.value;
-  WebPageBloc? get currentTab => _currentTab.value;
-  int? get currentTabIdx =>
-      (currentTab != null) ? _tabsList.indexOf(currentTab!) : null;
-  bool get isOnlyTab => _tabsList.length == 1;
-
-  WebPageBloc? get previousTab {
-    final idx = currentTabIdx;
-    if (idx == null) {
-      return null;
-    }
-    int prevIdx = idx - 1;
-    prevIdx = (prevIdx < 0) ? (_tabsList.length - 1) : prevIdx;
-    return _tabsList[prevIdx];
-  }
-
-  WebPageBloc? get nextTab {
-    final idx = currentTabIdx;
-    if (idx == null) {
-      return null;
-    }
-    int nextIdx = idx + 1;
-    nextIdx = (nextIdx > _tabsList.length - 1) ? 0 : nextIdx;
-    return _tabsList[nextIdx];
-  }
-
-  // Sinks
-  final _tabsActionController = StreamController<TabsAction>();
-  Sink<TabsAction> get request => _tabsActionController.sink;
-
-  TabsBloc({required this.tabFactory, required this.disposeTab}) {
-    _tabsActionController.stream.listen(_onTabsActionChanged);
-  }
-
-  void dispose() {
-    _tabsList.forEach(disposeTab);
-    _tabsActionController.close();
-  }
-
-  void _onTabsActionChanged(TabsAction action) {
-    switch (action.op) {
-      case TabsActionType.newTab:
-        final tab = tabFactory();
-        _tabsList.add(tab);
-        _tabs.value = UnmodifiableListView<WebPageBloc>(_tabsList);
-        _currentTab.value = tab;
-        break;
-      case TabsActionType.focusTab:
-        final focusTab = action as FocusTabAction;
-        _currentTab.value = focusTab.tab;
-        break;
-      case TabsActionType.removeTab:
-        if (tabs.isEmpty) {
-          break;
-        }
-
-        final removeTab = action as RemoveTabAction;
-        final tab = removeTab.tab;
-
-        if (tabs.length == 1) {
-          _currentTab.value = null;
-        } else if (currentTab == removeTab.tab) {
-          final indexOfRemoved = _tabsList.indexOf(tab);
-          final indexOfNewTab =
-              indexOfRemoved == 0 ? indexOfRemoved + 1 : indexOfRemoved - 1;
-          _currentTab.value = _tabsList.elementAt(indexOfNewTab);
-        }
-
-        _tabsList.remove(tab);
-        _tabs.value = UnmodifiableListView<WebPageBloc>(_tabsList);
-        disposeTab(tab);
-        break;
-      case TabsActionType.addTab:
-        final addTabAction = action as AddTabAction;
-        _tabsList.add(addTabAction.tab);
-        _tabs.value = UnmodifiableListView<WebPageBloc>(_tabsList);
-        _currentTab.value = addTabAction.tab;
-        break;
-      case TabsActionType.rearrangeTabs:
-        final rearrangeTabs = action as RearrangeTabsAction;
-        final originalIndex = rearrangeTabs.originalIndex;
-        final newIndex = rearrangeTabs.newIndex;
-
-        final movingTab = _tabsList.elementAt(originalIndex);
-        // the tab moves to the left.
-        if (originalIndex > newIndex) {
-          _tabsList
-            ..removeAt(originalIndex)
-            ..insert(newIndex, movingTab);
-        }
-        // the tab moves to the right.
-        else {
-          _tabsList
-            ..insert(newIndex + 1, movingTab)
-            ..removeAt(originalIndex);
-        }
-        _tabs.value = UnmodifiableListView<WebPageBloc>(_tabsList);
-        break;
-    }
-  }
-}
diff --git a/bin/simple_browser/lib/src/blocs/webpage_bloc.dart b/bin/simple_browser/lib/src/blocs/webpage_bloc.dart
deleted file mode 100644
index 95712d5..0000000
--- a/bin/simple_browser/lib/src/blocs/webpage_bloc.dart
+++ /dev/null
@@ -1,106 +0,0 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'dart:async';
-import 'package:flutter/foundation.dart';
-import 'package:fuchsia_logger/logger.dart';
-import 'package:fuchsia_scenic_flutter/fuchsia_view.dart'
-    show FuchsiaViewConnection;
-import '../models/webpage_action.dart';
-import '../services/simple_browser_web_service.dart';
-import '../utils/sanitize_url.dart';
-
-enum PageType { empty, normal, error }
-
-/// Business logic for the webpage.
-/// Sinks:
-///   WebPageAction: a browsing action - url request, prev/next page, etc.
-/// Value Notifiers:
-///   url: the current url.
-///   forwardState: bool indicating whether forward action is available.
-///   backState: bool indicating whether back action is available.
-///   isLoadedState: bool indicating whether main document has fully loaded.
-///   pageTitle: the current page title.
-///   pageType: the current type of the page; either normal, error, or empty.
-class WebPageBloc {
-  final SimpleBrowserWebService webService;
-
-  /// Used to present webpage in Flutter FuchsiaView
-  FuchsiaViewConnection get fuchsiaViewConnection =>
-      webService.fuchsiaViewConnection;
-
-  ChangeNotifier get urlNotifier =>
-      webService.navigationEventListener.urlNotifier;
-  ChangeNotifier get forwardStateNotifier =>
-      webService.navigationEventListener.forwardStateNotifier;
-  ChangeNotifier get backStateNotifier =>
-      webService.navigationEventListener.backStateNotifier;
-  ChangeNotifier get isLoadedStateNotifier =>
-      webService.navigationEventListener.isLoadedStateNotifier;
-  ChangeNotifier get pageTitleNotifier =>
-      webService.navigationEventListener.pageTitleNotifier;
-  ChangeNotifier get pageTypeNotifier =>
-      webService.navigationEventListener.pageTypeNotifier;
-
-  String get url => webService.navigationEventListener.url;
-  bool get forwardState => webService.navigationEventListener.forwardState;
-  bool get backState => webService.navigationEventListener.backState;
-  bool get isLoadedState => webService.navigationEventListener.isLoadedState;
-  String? get pageTitle => webService.navigationEventListener.pageTitle;
-  PageType get pageType => webService.navigationEventListener.pageType;
-  // Sinks
-  final _webPageActionController = StreamController<WebPageAction>();
-  Sink<WebPageAction> get request => _webPageActionController.sink;
-
-  // Constructors
-
-  /// Creates a new [WebPageBloc] with a new page from [ContextProxy].
-  ///
-  /// A basic constructor for creating a brand-new tab.
-  /// Can also be used for testing purposes and in this case,
-  /// context parameter does not need to be set.
-  WebPageBloc({
-    required this.webService,
-    String? homePage,
-  }) {
-    if (homePage != null) {
-      _onWebPageActionChanged(NavigateToAction(url: homePage));
-    }
-
-    /// Begins handling action requests
-    _webPageActionController.stream.listen(_onWebPageActionChanged);
-  }
-
-  void dispose() {
-    webService.dispose();
-    _webPageActionController.close();
-  }
-
-  Future<void> _onWebPageActionChanged(WebPageAction action) async {
-    switch (action.op) {
-      case WebPageActionType.navigateTo:
-        final navigate = action as NavigateToAction;
-        await webService.loadUrl(
-          sanitizeUrl(navigate.url),
-        );
-        break;
-      case WebPageActionType.goBack:
-        await webService.goBack();
-        break;
-      case WebPageActionType.goForward:
-        await webService.goForward();
-        break;
-      case WebPageActionType.refresh:
-        await webService.refresh();
-        break;
-      case WebPageActionType.setFocus:
-        try {
-          await fuchsiaViewConnection.requestFocus();
-        } on Exception catch (e) {
-          log.warning('Failed to set focus on the current web view: $e');
-        }
-        break;
-    }
-  }
-}
diff --git a/bin/simple_browser/lib/src/models/app_model.dart b/bin/simple_browser/lib/src/models/app_model.dart
deleted file mode 100644
index d0ac5cf..0000000
--- a/bin/simple_browser/lib/src/models/app_model.dart
+++ /dev/null
@@ -1,123 +0,0 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-import 'dart:async';
-import 'package:fidl_fuchsia_intl/fidl_async.dart';
-import 'package:flutter/material.dart';
-import 'package:fuchsia_internationalization_flutter/internationalization.dart';
-import 'package:fuchsia_scenic/views.dart';
-import 'package:fuchsia_services/services.dart';
-import 'package:keyboard_shortcuts/keyboard_shortcuts.dart';
-import '../blocs/tabs_bloc.dart';
-import '../models/tabs_action.dart';
-import '../models/webpage_action.dart';
-import '../utils/browser_shortcuts.dart';
-
-/// Model that handles the browser's tab and webpage states.
-///
-/// tabsBloc: A listener for the tab and web page states.
-/// localeStream: A getter for the current localization value.
-/// initialLocale: A getter for the initial localization value.
-/// keyboardShortcuts: A getter for the browser's [KeyboardShortcuts].
-class AppModel {
-  final TabsBloc tabsBloc;
-  Stream<Locale> _localeStream;
-  late final FocusNode fieldFocus;
-  late final KeyboardShortcuts? _keyboardShortcuts;
-
-  AppModel({
-    required this.tabsBloc,
-    required Stream<Locale> localeStream,
-  }) : _localeStream = localeStream {
-    fieldFocus = FocusNode();
-    _keyboardShortcuts =
-        // TODO(https://fxbug.dev/71711): Figure out why `dart analyze`
-        // complains about this.
-        // ignore: argument_type_not_assignable
-        BrowserShortcuts(tabsBloc: tabsBloc, actions: _shortcutActions())
-            .activateShortcuts(ScenicContext.hostViewRef());
-  }
-
-  factory AppModel.fromStartupContext({required TabsBloc tabsBloc}) {
-    final _intl = PropertyProviderProxy();
-    Incoming.fromSvcPath()
-      ..connectToService(_intl)
-      ..close();
-    final localStream = LocaleSource(_intl).stream().asBroadcastStream();
-
-    return AppModel(
-      tabsBloc: tabsBloc,
-      localeStream: localStream,
-    );
-  }
-
-  Stream<Locale> get localeStream => _localeStream;
-
-  KeyboardShortcuts? get keyboardShortcuts => _keyboardShortcuts;
-
-  void newTab() => tabsBloc.request.add(NewTabAction());
-
-  Map<String, VoidCallback> _shortcutActions() {
-    return {
-      'newTab': newTab,
-      'closeTab': _closeTab,
-      'goBack': _goBack,
-      'goForward': _goForward,
-      'refresh': _refresh,
-      'previousTab': _previousTab,
-      'nextTab': _nextTab,
-      'focusField': _focusField,
-    };
-  }
-
-  void _closeTab() {
-    if (tabsBloc.isOnlyTab) {
-      return;
-    }
-    tabsBloc.request.add(RemoveTabAction(tab: tabsBloc.currentTab!));
-  }
-
-  void _goBack() {
-    if (tabsBloc.currentTab!.backState) {
-      tabsBloc.currentTab!.request.add(GoBackAction());
-      return;
-    }
-    return;
-  }
-
-  void _goForward() {
-    if (tabsBloc.currentTab!.forwardState) {
-      tabsBloc.currentTab!.request.add(GoForwardAction());
-      return;
-    }
-    return;
-  }
-
-  void _refresh() => tabsBloc.currentTab!.request.add(RefreshAction());
-
-  void _previousTab() {
-    if (tabsBloc.isOnlyTab || tabsBloc.previousTab == null) {
-      return;
-    }
-    tabsBloc.request.add(FocusTabAction(tab: tabsBloc.previousTab!));
-  }
-
-  void _nextTab() {
-    if (tabsBloc.isOnlyTab || tabsBloc.nextTab == null) {
-      return;
-    }
-    tabsBloc.request.add(FocusTabAction(tab: tabsBloc.nextTab!));
-  }
-
-  void _focusField() => fieldFocus.requestFocus();
-
-  // ignore: avoid_positional_boolean_parameters
-  void onFocus(bool focused) {
-    if (focused && tabsBloc.currentTab!.webService.isLoaded) {
-      tabsBloc.currentTab!.request.add(SetFocusAction());
-    }
-  }
-
-  // TODO: Activate/Deactivate the keyboardShortcuts depending on the browser's
-  // focus state when its relevant support is ready (fxb/42185)
-}
diff --git a/bin/simple_browser/lib/src/models/tabs_action.dart b/bin/simple_browser/lib/src/models/tabs_action.dart
deleted file mode 100644
index 6643034..0000000
--- a/bin/simple_browser/lib/src/models/tabs_action.dart
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import '../blocs/webpage_bloc.dart';
-
-// Base class for actions handled by the tabs BLoC
-class TabsAction {
-  final TabsActionType op;
-  const TabsAction(this.op);
-}
-
-// Operations allowed for tab management
-enum TabsActionType { newTab, removeTab, focusTab, addTab, rearrangeTabs }
-
-// Instructs to add a new tab to tab list.
-class NewTabAction extends TabsAction {
-  const NewTabAction() : super(TabsActionType.newTab);
-}
-
-// Instructs to remove a specific tab.
-class RemoveTabAction extends TabsAction {
-  final WebPageBloc tab;
-  const RemoveTabAction({required this.tab}) : super(TabsActionType.removeTab);
-}
-
-// Instructs to focus a specific tab.
-class FocusTabAction extends TabsAction {
-  final WebPageBloc tab;
-  const FocusTabAction({required this.tab}) : super(TabsActionType.focusTab);
-}
-
-// Instructs to add an existing tab to the tab list.
-class AddTabAction extends TabsAction {
-  final WebPageBloc tab;
-  const AddTabAction({required this.tab}) : super(TabsActionType.addTab);
-}
-
-class RearrangeTabsAction extends TabsAction {
-  final int originalIndex;
-  final int newIndex;
-  const RearrangeTabsAction({
-    required this.originalIndex,
-    required this.newIndex,
-  }) : super(TabsActionType.rearrangeTabs);
-}
diff --git a/bin/simple_browser/lib/src/models/webpage_action.dart b/bin/simple_browser/lib/src/models/webpage_action.dart
deleted file mode 100644
index 24e0a35..0000000
--- a/bin/simple_browser/lib/src/models/webpage_action.dart
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// Base class for actions handled by the application's BLOC
-class WebPageAction {
-  final WebPageActionType op;
-  const WebPageAction(this.op);
-}
-
-// Operations allowed for browsing
-enum WebPageActionType { goForward, goBack, refresh, navigateTo, setFocus }
-
-// Instructs to go to the next page.
-class GoForwardAction extends WebPageAction {
-  const GoForwardAction() : super(WebPageActionType.goForward);
-}
-
-// Instructs to go to the previous page.
-class GoBackAction extends WebPageAction {
-  const GoBackAction() : super(WebPageActionType.goBack);
-}
-
-// Instructs to refresh the current page.
-class RefreshAction extends WebPageAction {
-  const RefreshAction() : super(WebPageActionType.refresh);
-}
-
-// Instructs to navigate to some url.
-class NavigateToAction extends WebPageAction {
-  final String url;
-  NavigateToAction({required this.url}) : super(WebPageActionType.navigateTo);
-}
-
-// Instructs to set focus on webview.
-class SetFocusAction extends WebPageAction {
-  const SetFocusAction() : super(WebPageActionType.setFocus);
-}
diff --git a/bin/simple_browser/lib/src/services/simple_browser_navigation_event_listener.dart b/bin/simple_browser/lib/src/services/simple_browser_navigation_event_listener.dart
deleted file mode 100644
index 83e3d3b..0000000
--- a/bin/simple_browser/lib/src/services/simple_browser_navigation_event_listener.dart
+++ /dev/null
@@ -1,77 +0,0 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'dart:async';
-import 'package:fidl_fuchsia_web/fidl_async.dart' as web;
-import 'package:flutter/foundation.dart';
-
-import 'package:fuchsia_logger/logger.dart';
-
-import '../blocs/webpage_bloc.dart';
-
-class SimpleBrowserNavigationEventListener extends web.NavigationEventListener {
-  // Value Notifiers
-  final _url = ValueNotifier<String>('');
-  final _forwardState = ValueNotifier<bool>(false);
-  final _backState = ValueNotifier<bool>(false);
-  final _isLoadedState = ValueNotifier<bool>(true);
-  final _pageTitle = ValueNotifier<String?>(null);
-  final _pageType = ValueNotifier<PageType>(PageType.empty);
-
-  ChangeNotifier get urlNotifier => _url;
-  ChangeNotifier get forwardStateNotifier => _forwardState;
-  ChangeNotifier get backStateNotifier => _backState;
-  ChangeNotifier get isLoadedStateNotifier => _isLoadedState;
-  ChangeNotifier get pageTitleNotifier => _pageTitle;
-  ChangeNotifier get pageTypeNotifier => _pageType;
-
-  String get url => _url.value;
-  bool get forwardState => _forwardState.value;
-  bool get backState => _backState.value;
-  bool get isLoadedState => _isLoadedState.value;
-  String? get pageTitle => _pageTitle.value;
-  PageType get pageType => _pageType.value;
-
-  SimpleBrowserNavigationEventListener();
-
-  @override
-  Future<Null> onNavigationStateChanged(web.NavigationState event) async {
-    final url = event.url;
-    if (url != null) {
-      log.info('url loaded: $url');
-      _url.value = url;
-    }
-    final canGoForward = event.canGoForward;
-    if (canGoForward != null) {
-      _forwardState.value = canGoForward;
-    }
-    final canGoBack = event.canGoBack;
-    if (canGoBack != null) {
-      _backState.value = canGoBack;
-    }
-    final isLoaded = event.isMainDocumentLoaded;
-    if (isLoaded != null) {
-      _isLoadedState.value = isLoaded;
-    }
-    final title = event.title;
-    if (title != null) {
-      _pageTitle.value = title;
-    }
-    final type = event.pageType;
-    if (type != null) {
-      _pageType.value = pageTypeForWebPageType(type);
-    }
-  }
-
-  PageType pageTypeForWebPageType(web.PageType pageType) {
-    switch (pageType) {
-      case web.PageType.normal:
-        return PageType.normal;
-      case web.PageType.error:
-        return PageType.error;
-      default:
-        return PageType.empty;
-    }
-  }
-}
diff --git a/bin/simple_browser/lib/src/services/simple_browser_web_service.dart b/bin/simple_browser/lib/src/services/simple_browser_web_service.dart
deleted file mode 100644
index 14d89c8..0000000
--- a/bin/simple_browser/lib/src/services/simple_browser_web_service.dart
+++ /dev/null
@@ -1,138 +0,0 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'dart:async';
-import 'package:fidl/fidl.dart' show InterfaceHandle;
-import 'package:fidl_fuchsia_ui_views/fidl_async.dart' as views;
-import 'package:fidl_fuchsia_web/fidl_async.dart' as web;
-import 'package:fuchsia_scenic/views.dart';
-import 'package:fuchsia_scenic_flutter/fuchsia_view.dart'
-    show FuchsiaViewConnection;
-import 'package:zircon/zircon.dart';
-
-import '../blocs/webpage_bloc.dart';
-import 'simple_browser_navigation_event_listener.dart';
-
-class SimpleBrowserWebService {
-  final web.FrameProxy _frame;
-  final _navigationController = web.NavigationControllerProxy();
-  final _navigationEventObserverBinding = web.NavigationEventListenerBinding();
-  final _popupFrameCreationObserverBinding =
-      web.PopupFrameCreationListenerBinding();
-  final _simpleBrowserNavigationEventListener =
-      SimpleBrowserNavigationEventListener();
-
-  /// Used to present webpage in Flutter FuchsiaView
-  late FuchsiaViewConnection _fuchsiaViewConnection;
-  bool _rendered = false;
-
-  FuchsiaViewConnection get fuchsiaViewConnection => _fuchsiaViewConnection;
-  late views.ViewHolderToken _viewHolderToken;
-  bool get isLoaded => _rendered;
-  SimpleBrowserNavigationEventListener get navigationEventListener =>
-      _simpleBrowserNavigationEventListener;
-
-  factory SimpleBrowserWebService({
-    required web.ContextProxy context,
-    required void Function(WebPageBloc webPageBloc) popupHandler,
-    void Function()? onLoaded,
-  }) {
-    final frame = web.FrameProxy();
-    context.createFrame(frame.ctrl.request());
-    return SimpleBrowserWebService.withFrame(
-      frame: frame,
-      popupHandler: popupHandler,
-      onLoaded: onLoaded,
-    );
-  }
-
-  SimpleBrowserWebService.withFrame({
-    required web.FrameProxy frame,
-    required void Function(WebPageBloc webPageBloc) popupHandler,
-    void Function()? onLoaded,
-  }) : _frame = frame {
-    _frame
-
-      /// Sets up listeners and attaches navigation controller.
-      ..setNavigationEventListener(_navigationEventObserverBinding
-          .wrap(_simpleBrowserNavigationEventListener))
-      ..getNavigationController(_navigationController.ctrl.request())
-      ..setPopupFrameCreationListener(
-        _popupFrameCreationObserverBinding.wrap(
-          _PopupListener(popupHandler, onLoaded: onLoaded),
-        ),
-      );
-
-    /// Creates a token pair for the newly-created View.
-    final tokenPair = ViewTokenPair();
-    final viewRefPair = EventPairPair();
-    assert(viewRefPair.status == ZX.OK);
-
-    _viewHolderToken = tokenPair.viewHolderToken;
-
-    final viewRef =
-        views.ViewRef(reference: viewRefPair.first!.duplicate(ZX.RIGHTS_BASIC));
-    final viewRefControl = views.ViewRefControl(
-      reference: viewRefPair.second!
-          .duplicate(ZX.DEFAULT_EVENTPAIR_RIGHTS & (~ZX.RIGHT_DUPLICATE)),
-    );
-    final viewRefInject =
-        views.ViewRef(reference: viewRefPair.first!.duplicate(ZX.RIGHTS_BASIC));
-
-    _frame.createViewWithViewRef(tokenPair.viewToken, viewRefControl, viewRef);
-    _fuchsiaViewConnection = FuchsiaViewConnection(
-      _viewHolderToken,
-      viewRef: viewRefInject,
-      onViewStateChanged: (_, state) {
-        if (state == true && !_rendered) {
-          onLoaded?.call();
-          _rendered = true;
-        }
-      },
-    );
-  }
-
-  Future<void> enableConsoleLog() =>
-      _frame.setJavaScriptLogLevel(web.ConsoleLogLevel.debug);
-
-  void dispose() {
-    _navigationController.ctrl.close();
-    _frame.ctrl.close();
-  }
-
-  Future<void> loadUrl(String url) => _navigationController.loadUrl(
-        url,
-        web.LoadUrlParams(
-          type: web.LoadUrlReason.typed,
-          wasUserActivated: true,
-        ),
-      );
-  Future<void> goBack() => _navigationController.goBack();
-  Future<void> goForward() => _navigationController.goForward();
-  Future<void> refresh() =>
-      _navigationController.reload(web.ReloadType.partialCache);
-}
-
-class _PopupListener extends web.PopupFrameCreationListener {
-  final void Function(WebPageBloc webPageBloc) _handler;
-  final void Function()? _onLoaded;
-
-  _PopupListener(this._handler, {void Function()? onLoaded})
-      : _onLoaded = onLoaded;
-
-  @override
-  Future<void> onPopupFrameCreated(
-    InterfaceHandle<web.Frame> frame,
-    web.PopupFrameCreationInfo info,
-  ) async {
-    final webService = SimpleBrowserWebService.withFrame(
-      frame: web.FrameProxy()..ctrl.bind(frame),
-      popupHandler: _handler,
-      onLoaded: _onLoaded,
-    );
-    _handler(
-      WebPageBloc(webService: webService),
-    );
-  }
-}
diff --git a/bin/simple_browser/lib/src/utils/browser_shortcuts.dart b/bin/simple_browser/lib/src/utils/browser_shortcuts.dart
deleted file mode 100644
index 25cea89..0000000
--- a/bin/simple_browser/lib/src/utils/browser_shortcuts.dart
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-import 'dart:io';
-
-import 'package:fidl_fuchsia_ui_shortcut/fidl_async.dart' as ui_shortcut
-    show RegistryProxy;
-import 'package:fidl_fuchsia_ui_views/fidl_async.dart' show ViewRef;
-import 'package:flutter/material.dart';
-import 'package:fuchsia_logger/logger.dart';
-import 'package:fuchsia_services/services.dart';
-import 'package:keyboard_shortcuts/keyboard_shortcuts.dart';
-import 'package:simple_browser/src/blocs/tabs_bloc.dart';
-
-const path = '/pkg/data/keyboard_shortcuts.json';
-
-class BrowserShortcuts {
-  final TabsBloc tabsBloc;
-  late ui_shortcut.RegistryProxy registryProxy;
-  final Map<String, VoidCallback> actions;
-
-  factory BrowserShortcuts({
-    required TabsBloc tabsBloc,
-    required Map<String, VoidCallback> actions,
-    ui_shortcut.RegistryProxy? shortcutRegistry,
-  }) {
-    if (shortcutRegistry == null) {
-      return BrowserShortcuts._fromStartupContext(
-        tabsBloc: tabsBloc,
-        actions: actions,
-      );
-    }
-    return BrowserShortcuts._afterStartupContext(
-      tabsBloc: tabsBloc,
-      actions: actions,
-    );
-  }
-
-  BrowserShortcuts._fromStartupContext({
-    required this.tabsBloc,
-    required this.actions,
-  }) {
-    registryProxy = ui_shortcut.RegistryProxy();
-    Incoming.fromSvcPath()
-      ..connectToService(registryProxy)
-      ..close();
-  }
-
-  BrowserShortcuts._afterStartupContext({
-    required this.tabsBloc,
-    required this.actions,
-  });
-
-  KeyboardShortcuts? activateShortcuts(ViewRef viewRef) {
-    File file = File(path);
-    file.readAsString().then((bindings) {
-      return KeyboardShortcuts(
-        registry: registryProxy,
-        actions: actions,
-        bindings: bindings,
-        viewRef: viewRef,
-      );
-    }).catchError((err) {
-      log.shout('$err: Failed to activate keyboard shortcuts.');
-    });
-    return null;
-  }
-}
diff --git a/bin/simple_browser/lib/src/utils/sanitize_url.dart b/bin/simple_browser/lib/src/utils/sanitize_url.dart
deleted file mode 100644
index 364a038..0000000
--- a/bin/simple_browser/lib/src/utils/sanitize_url.dart
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'tld_checker.dart';
-
-String sanitizeUrl(String url) {
-  // Checks if the input starts with a scheme.
-  String scheme;
-  try {
-    scheme = Uri.parse(url).scheme;
-  } on FormatException {
-    return googleKeyword(url);
-  }
-
-  // Checks if the scheme is valid.
-  // We currently only supports http, https and chrome.
-  // localhost is included in the validSchemePattern since it is recognized
-  // as a scheme by the dart Uri.parse method whene there is no other scheme.
-  final validSchemePattern = RegExp(r'^(https?|chrome|localhost)$');
-  if (scheme.isNotEmpty) {
-    if (validSchemePattern.hasMatch(scheme)) {
-      return url;
-    }
-    return googleKeyword(url);
-  }
-
-  // Adds a scheme to get a more accurate output from Uri.parse().host
-  String schemedUrl = 'https://$url';
-  String hostUrl = Uri.parse(schemedUrl).host;
-
-  // Checks if the host url has a valid pattern.
-  // Uri.parse().host does not check the validity.
-  final validHostPattern = RegExp(r'([a-zA-Z0-9@_-]{1,256}[\.]{1,1})+[\w]+');
-  if (validHostPattern.stringMatch(hostUrl) != hostUrl) {
-    return googleKeyword(url);
-  }
-
-  // Checks if the URL has a valid TLD.
-  String tld = hostUrl.split('.').last;
-  if (TldChecker().isValid(tld)) {
-    return schemedUrl;
-  }
-
-  // Checks if the URL is IPv4 address.
-  final validIpv4Pattern = RegExp(
-      r'\b((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.){3,3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])$');
-  if (validIpv4Pattern.stringMatch(hostUrl) == hostUrl) {
-    return schemedUrl;
-  }
-
-  return googleKeyword(url);
-}
-
-String googleKeyword(String keyword) =>
-    'https://www.google.com/search?q=${Uri.encodeQueryComponent(keyword)}';
diff --git a/bin/simple_browser/lib/src/utils/tld_checker.dart b/bin/simple_browser/lib/src/utils/tld_checker.dart
deleted file mode 100644
index fcc9ff5..0000000
--- a/bin/simple_browser/lib/src/utils/tld_checker.dart
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'package:fuchsia_logger/logger.dart';
-import 'package:meta/meta.dart';
-import 'tlds_provider.dart';
-import 'valid_tlds.dart';
-
-class TldChecker {
-  late List<String> _validTlds;
-
-  /// A flag that indicates if the valid TLD list is loaded or not.
-  ///
-  /// Its default value on the initilization is 'false'. and is set to 'true'
-  /// once the [prefetchTlds()] is called, and never changes unless the browser
-  /// is relaunched and this [TldChecker] is newly initiated.
-  late bool _isIanaTldsLoaded;
-
-  static final TldChecker _tldCheckerInstance = TldChecker._create();
-  factory TldChecker() {
-    return _tldCheckerInstance;
-  }
-
-  TldChecker._create() {
-    _validTlds = kValidTlds;
-    _isIanaTldsLoaded = false;
-    log.info('A singleton TldChecker instance has been created.');
-  }
-
-  /// Fetches a valid TLD list from the IANA if it has not loaded yet.
-  ///
-  /// If a List<String> type parameter is given, it does not fetch the TLD list
-  /// from the web and instead, just uses the parameter list as the valid TLD
-  /// list. Therefore, this parameter should be given only for testing purposes.
-  void prefetchTlds({List<String>? testTlds}) async {
-    if (testTlds != null) {
-      _validTlds = testTlds;
-    } else {
-      if (!_isIanaTldsLoaded) {
-        _validTlds = await TldsProvider().fetchTldsList() ?? kValidTlds;
-        _isIanaTldsLoaded = true;
-      } else {
-        log.warning(
-            'TLD List is already loaded. You do not need to fetch it again.');
-      }
-    }
-  }
-
-  bool isValid(String tld) => _validTlds.contains(tld.toUpperCase());
-
-  @visibleForTesting
-  List<String> get validTlds => _validTlds;
-}
diff --git a/bin/simple_browser/lib/src/utils/tlds_provider.dart b/bin/simple_browser/lib/src/utils/tlds_provider.dart
deleted file mode 100644
index d34af10..0000000
--- a/bin/simple_browser/lib/src/utils/tlds_provider.dart
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'dart:async';
-import 'package:fuchsia_logger/logger.dart';
-import 'package:http/http.dart' as http;
-
-class TldsProvider {
-  String? _data;
-
-  // Creates the TldsModel Once when the simple browser is initiated.
-  Future<String?> loadIanaTldsList() async {
-    try {
-      final response = await http
-          .get(Uri.parse('http://data.iana.org/TLD/tlds-alpha-by-domain.txt'));
-
-      if (response.statusCode == 200) {
-        log.info('Successfully loaded a TLD list from iana.org');
-        return response.body;
-      } else {
-        log.warning('Failed to load a TLD list from iana.org '
-            '(Bad response: ${response.statusCode})');
-        return null;
-      }
-      // ignore: avoid_catches_without_on_clauses
-    } catch (e) {
-      log.severe('Failed to load a TLD list from iana.org ($e)');
-      return null;
-    }
-  }
-
-  Future<List<String>?> fetchTldsList() async {
-    String? data;
-
-    data = _data ?? await loadIanaTldsList();
-
-    if (data == null) {
-      return null;
-    }
-
-    List<String> tldsList = data.split('\n');
-
-    // Removes all white spaces.
-    for (int i = 0; i < tldsList.length; i++) {
-      tldsList[i] = tldsList[i].replaceAll(RegExp(r'\s+'), '');
-    }
-
-    // Removes all comments and empty elements.
-    tldsList.removeWhere((item) => item.isEmpty || item.startsWith('#'));
-
-    return tldsList;
-  }
-
-  set data(String testData) => _data = testData;
-}
diff --git a/bin/simple_browser/lib/src/utils/valid_tlds.dart b/bin/simple_browser/lib/src/utils/valid_tlds.dart
deleted file mode 100644
index 30d39af..0000000
--- a/bin/simple_browser/lib/src/utils/valid_tlds.dart
+++ /dev/null
@@ -1,1533 +0,0 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-const kValidTlds = <String>[
-  'AAA',
-  'AARP',
-  'ABARTH',
-  'ABB',
-  'ABBOTT',
-  'ABBVIE',
-  'ABC',
-  'ABLE',
-  'ABOGADO',
-  'ABUDHABI',
-  'AC',
-  'ACADEMY',
-  'ACCENTURE',
-  'ACCOUNTANT',
-  'ACCOUNTANTS',
-  'ACO',
-  'ACTOR',
-  'AD',
-  'ADAC',
-  'ADS',
-  'ADULT',
-  'AE',
-  'AEG',
-  'AERO',
-  'AETNA',
-  'AF',
-  'AFAMILYCOMPANY',
-  'AFL',
-  'AFRICA',
-  'AG',
-  'AGAKHAN',
-  'AGENCY',
-  'AI',
-  'AIG',
-  'AIGO',
-  'AIRBUS',
-  'AIRFORCE',
-  'AIRTEL',
-  'AKDN',
-  'AL',
-  'ALFAROMEO',
-  'ALIBABA',
-  'ALIPAY',
-  'ALLFINANZ',
-  'ALLSTATE',
-  'ALLY',
-  'ALSACE',
-  'ALSTOM',
-  'AM',
-  'AMERICANEXPRESS',
-  'AMERICANFAMILY',
-  'AMEX',
-  'AMFAM',
-  'AMICA',
-  'AMSTERDAM',
-  'ANALYTICS',
-  'ANDROID',
-  'ANQUAN',
-  'ANZ',
-  'AO',
-  'AOL',
-  'APARTMENTS',
-  'APP',
-  'APPLE',
-  'AQ',
-  'AQUARELLE',
-  'AR',
-  'ARAB',
-  'ARAMCO',
-  'ARCHI',
-  'ARMY',
-  'ARPA',
-  'ART',
-  'ARTE',
-  'AS',
-  'ASDA',
-  'ASIA',
-  'ASSOCIATES',
-  'AT',
-  'ATHLETA',
-  'ATTORNEY',
-  'AU',
-  'AUCTION',
-  'AUDI',
-  'AUDIBLE',
-  'AUDIO',
-  'AUSPOST',
-  'AUTHOR',
-  'AUTO',
-  'AUTOS',
-  'AVIANCA',
-  'AW',
-  'AWS',
-  'AX',
-  'AXA',
-  'AZ',
-  'AZURE',
-  'BA',
-  'BABY',
-  'BAIDU',
-  'BANAMEX',
-  'BANANAREPUBLIC',
-  'BAND',
-  'BANK',
-  'BAR',
-  'BARCELONA',
-  'BARCLAYCARD',
-  'BARCLAYS',
-  'BAREFOOT',
-  'BARGAINS',
-  'BASEBALL',
-  'BASKETBALL',
-  'BAUHAUS',
-  'BAYERN',
-  'BB',
-  'BBC',
-  'BBT',
-  'BBVA',
-  'BCG',
-  'BCN',
-  'BD',
-  'BE',
-  'BEATS',
-  'BEAUTY',
-  'BEER',
-  'BENTLEY',
-  'BERLIN',
-  'BEST',
-  'BESTBUY',
-  'BET',
-  'BF',
-  'BG',
-  'BH',
-  'BHARTI',
-  'BI',
-  'BIBLE',
-  'BID',
-  'BIKE',
-  'BING',
-  'BINGO',
-  'BIO',
-  'BIZ',
-  'BJ',
-  'BLACK',
-  'BLACKFRIDAY',
-  'BLOCKBUSTER',
-  'BLOG',
-  'BLOOMBERG',
-  'BLUE',
-  'BM',
-  'BMS',
-  'BMW',
-  'BN',
-  'BNPPARIBAS',
-  'BO',
-  'BOATS',
-  'BOEHRINGER',
-  'BOFA',
-  'BOM',
-  'BOND',
-  'BOO',
-  'BOOK',
-  'BOOKING',
-  'BOSCH',
-  'BOSTIK',
-  'BOSTON',
-  'BOT',
-  'BOUTIQUE',
-  'BOX',
-  'BR',
-  'BRADESCO',
-  'BRIDGESTONE',
-  'BROADWAY',
-  'BROKER',
-  'BROTHER',
-  'BRUSSELS',
-  'BS',
-  'BT',
-  'BUDAPEST',
-  'BUGATTI',
-  'BUILD',
-  'BUILDERS',
-  'BUSINESS',
-  'BUY',
-  'BUZZ',
-  'BV',
-  'BW',
-  'BY',
-  'BZ',
-  'BZH',
-  'CA',
-  'CAB',
-  'CAFE',
-  'CAL',
-  'CALL',
-  'CALVINKLEIN',
-  'CAM',
-  'CAMERA',
-  'CAMP',
-  'CANCERRESEARCH',
-  'CANON',
-  'CAPETOWN',
-  'CAPITAL',
-  'CAPITALONE',
-  'CAR',
-  'CARAVAN',
-  'CARDS',
-  'CARE',
-  'CAREER',
-  'CAREERS',
-  'CARS',
-  'CARTIER',
-  'CASA',
-  'CASE',
-  'CASEIH',
-  'CASH',
-  'CASINO',
-  'CAT',
-  'CATERING',
-  'CATHOLIC',
-  'CBA',
-  'CBN',
-  'CBRE',
-  'CBS',
-  'CC',
-  'CD',
-  'CEB',
-  'CENTER',
-  'CEO',
-  'CERN',
-  'CF',
-  'CFA',
-  'CFD',
-  'CG',
-  'CH',
-  'CHANEL',
-  'CHANNEL',
-  'CHARITY',
-  'CHASE',
-  'CHAT',
-  'CHEAP',
-  'CHINTAI',
-  'CHRISTMAS',
-  'CHROME',
-  'CHRYSLER',
-  'CHURCH',
-  'CI',
-  'CIPRIANI',
-  'CIRCLE',
-  'CISCO',
-  'CITADEL',
-  'CITI',
-  'CITIC',
-  'CITY',
-  'CITYEATS',
-  'CK',
-  'CL',
-  'CLAIMS',
-  'CLEANING',
-  'CLICK',
-  'CLINIC',
-  'CLINIQUE',
-  'CLOTHING',
-  'CLOUD',
-  'CLUB',
-  'CLUBMED',
-  'CM',
-  'CN',
-  'CO',
-  'COACH',
-  'CODES',
-  'COFFEE',
-  'COLLEGE',
-  'COLOGNE',
-  'COM',
-  'COMCAST',
-  'COMMBANK',
-  'COMMUNITY',
-  'COMPANY',
-  'COMPARE',
-  'COMPUTER',
-  'COMSEC',
-  'CONDOS',
-  'CONSTRUCTION',
-  'CONSULTING',
-  'CONTACT',
-  'CONTRACTORS',
-  'COOKING',
-  'COOKINGCHANNEL',
-  'COOL',
-  'COOP',
-  'CORSICA',
-  'COUNTRY',
-  'COUPON',
-  'COUPONS',
-  'COURSES',
-  'CPA',
-  'CR',
-  'CREDIT',
-  'CREDITCARD',
-  'CREDITUNION',
-  'CRICKET',
-  'CROWN',
-  'CRS',
-  'CRUISE',
-  'CRUISES',
-  'CSC',
-  'CU',
-  'CUISINELLA',
-  'CV',
-  'CW',
-  'CX',
-  'CY',
-  'CYMRU',
-  'CYOU',
-  'CZ',
-  'DABUR',
-  'DAD',
-  'DANCE',
-  'DATA',
-  'DATE',
-  'DATING',
-  'DATSUN',
-  'DAY',
-  'DCLK',
-  'DDS',
-  'DE',
-  'DEAL',
-  'DEALER',
-  'DEALS',
-  'DEGREE',
-  'DELIVERY',
-  'DELL',
-  'DELOITTE',
-  'DELTA',
-  'DEMOCRAT',
-  'DENTAL',
-  'DENTIST',
-  'DESI',
-  'DESIGN',
-  'DEV',
-  'DHL',
-  'DIAMONDS',
-  'DIET',
-  'DIGITAL',
-  'DIRECT',
-  'DIRECTORY',
-  'DISCOUNT',
-  'DISCOVER',
-  'DISH',
-  'DIY',
-  'DJ',
-  'DK',
-  'DM',
-  'DNP',
-  'DO',
-  'DOCS',
-  'DOCTOR',
-  'DODGE',
-  'DOG',
-  'DOMAINS',
-  'DOT',
-  'DOWNLOAD',
-  'DRIVE',
-  'DTV',
-  'DUBAI',
-  'DUCK',
-  'DUNLOP',
-  'DUPONT',
-  'DURBAN',
-  'DVAG',
-  'DVR',
-  'DZ',
-  'EARTH',
-  'EAT',
-  'EC',
-  'ECO',
-  'EDEKA',
-  'EDU',
-  'EDUCATION',
-  'EE',
-  'EG',
-  'EMAIL',
-  'EMERCK',
-  'ENERGY',
-  'ENGINEER',
-  'ENGINEERING',
-  'ENTERPRISES',
-  'EPSON',
-  'EQUIPMENT',
-  'ER',
-  'ERICSSON',
-  'ERNI',
-  'ES',
-  'ESQ',
-  'ESTATE',
-  'ESURANCE',
-  'ET',
-  'ETISALAT',
-  'EU',
-  'EUROVISION',
-  'EUS',
-  'EVENTS',
-  'EVERBANK',
-  'EXCHANGE',
-  'EXPERT',
-  'EXPOSED',
-  'EXPRESS',
-  'EXTRASPACE',
-  'FAGE',
-  'FAIL',
-  'FAIRWINDS',
-  'FAITH',
-  'FAMILY',
-  'FAN',
-  'FANS',
-  'FARM',
-  'FARMERS',
-  'FASHION',
-  'FAST',
-  'FEDEX',
-  'FEEDBACK',
-  'FERRARI',
-  'FERRERO',
-  'FI',
-  'FIAT',
-  'FIDELITY',
-  'FIDO',
-  'FILM',
-  'FINAL',
-  'FINANCE',
-  'FINANCIAL',
-  'FIRE',
-  'FIRESTONE',
-  'FIRMDALE',
-  'FISH',
-  'FISHING',
-  'FIT',
-  'FITNESS',
-  'FJ',
-  'FK',
-  'FLICKR',
-  'FLIGHTS',
-  'FLIR',
-  'FLORIST',
-  'FLOWERS',
-  'FLY',
-  'FM',
-  'FO',
-  'FOO',
-  'FOOD',
-  'FOODNETWORK',
-  'FOOTBALL',
-  'FORD',
-  'FOREX',
-  'FORSALE',
-  'FORUM',
-  'FOUNDATION',
-  'FOX',
-  'FR',
-  'FREE',
-  'FRESENIUS',
-  'FRL',
-  'FROGANS',
-  'FRONTDOOR',
-  'FRONTIER',
-  'FTR',
-  'FUJITSU',
-  'FUJIXEROX',
-  'FUN',
-  'FUND',
-  'FURNITURE',
-  'FUTBOL',
-  'FYI',
-  'GA',
-  'GAL',
-  'GALLERY',
-  'GALLO',
-  'GALLUP',
-  'GAME',
-  'GAMES',
-  'GAP',
-  'GARDEN',
-  'GAY',
-  'GB',
-  'GBIZ',
-  'GD',
-  'GDN',
-  'GE',
-  'GEA',
-  'GENT',
-  'GENTING',
-  'GEORGE',
-  'GF',
-  'GG',
-  'GGEE',
-  'GH',
-  'GI',
-  'GIFT',
-  'GIFTS',
-  'GIVES',
-  'GIVING',
-  'GL',
-  'GLADE',
-  'GLASS',
-  'GLE',
-  'GLOBAL',
-  'GLOBO',
-  'GM',
-  'GMAIL',
-  'GMBH',
-  'GMO',
-  'GMX',
-  'GN',
-  'GODADDY',
-  'GOLD',
-  'GOLDPOINT',
-  'GOLF',
-  'GOO',
-  'GOODYEAR',
-  'GOOG',
-  'GOOGLE',
-  'GOP',
-  'GOT',
-  'GOV',
-  'GP',
-  'GQ',
-  'GR',
-  'GRAINGER',
-  'GRAPHICS',
-  'GRATIS',
-  'GREEN',
-  'GRIPE',
-  'GROCERY',
-  'GROUP',
-  'GS',
-  'GT',
-  'GU',
-  'GUARDIAN',
-  'GUCCI',
-  'GUGE',
-  'GUIDE',
-  'GUITARS',
-  'GURU',
-  'GW',
-  'GY',
-  'HAIR',
-  'HAMBURG',
-  'HANGOUT',
-  'HAUS',
-  'HBO',
-  'HDFC',
-  'HDFCBANK',
-  'HEALTH',
-  'HEALTHCARE',
-  'HELP',
-  'HELSINKI',
-  'HERE',
-  'HERMES',
-  'HGTV',
-  'HIPHOP',
-  'HISAMITSU',
-  'HITACHI',
-  'HIV',
-  'HK',
-  'HKT',
-  'HM',
-  'HN',
-  'HOCKEY',
-  'HOLDINGS',
-  'HOLIDAY',
-  'HOMEDEPOT',
-  'HOMEGOODS',
-  'HOMES',
-  'HOMESENSE',
-  'HONDA',
-  'HORSE',
-  'HOSPITAL',
-  'HOST',
-  'HOSTING',
-  'HOT',
-  'HOTELES',
-  'HOTELS',
-  'HOTMAIL',
-  'HOUSE',
-  'HOW',
-  'HR',
-  'HSBC',
-  'HT',
-  'HU',
-  'HUGHES',
-  'HYATT',
-  'HYUNDAI',
-  'IBM',
-  'ICBC',
-  'ICE',
-  'ICU',
-  'ID',
-  'IE',
-  'IEEE',
-  'IFM',
-  'IKANO',
-  'IL',
-  'IM',
-  'IMAMAT',
-  'IMDB',
-  'IMMO',
-  'IMMOBILIEN',
-  'IN',
-  'INC',
-  'INDUSTRIES',
-  'INFINITI',
-  'INFO',
-  'ING',
-  'INK',
-  'INSTITUTE',
-  'INSURANCE',
-  'INSURE',
-  'INT',
-  'INTEL',
-  'INTERNATIONAL',
-  'INTUIT',
-  'INVESTMENTS',
-  'IO',
-  'IPIRANGA',
-  'IQ',
-  'IR',
-  'IRISH',
-  'IS',
-  'ISMAILI',
-  'IST',
-  'ISTANBUL',
-  'IT',
-  'ITAU',
-  'ITV',
-  'IVECO',
-  'JAGUAR',
-  'JAVA',
-  'JCB',
-  'JCP',
-  'JE',
-  'JEEP',
-  'JETZT',
-  'JEWELRY',
-  'JIO',
-  'JLL',
-  'JM',
-  'JMP',
-  'JNJ',
-  'JO',
-  'JOBS',
-  'JOBURG',
-  'JOT',
-  'JOY',
-  'JP',
-  'JPMORGAN',
-  'JPRS',
-  'JUEGOS',
-  'JUNIPER',
-  'KAUFEN',
-  'KDDI',
-  'KE',
-  'KERRYHOTELS',
-  'KERRYLOGISTICS',
-  'KERRYPROPERTIES',
-  'KFH',
-  'KG',
-  'KH',
-  'KI',
-  'KIA',
-  'KIM',
-  'KINDER',
-  'KINDLE',
-  'KITCHEN',
-  'KIWI',
-  'KM',
-  'KN',
-  'KOELN',
-  'KOMATSU',
-  'KOSHER',
-  'KP',
-  'KPMG',
-  'KPN',
-  'KR',
-  'KRD',
-  'KRED',
-  'KUOKGROUP',
-  'KW',
-  'KY',
-  'KYOTO',
-  'KZ',
-  'LA',
-  'LACAIXA',
-  'LADBROKES',
-  'LAMBORGHINI',
-  'LAMER',
-  'LANCASTER',
-  'LANCIA',
-  'LANCOME',
-  'LAND',
-  'LANDROVER',
-  'LANXESS',
-  'LASALLE',
-  'LAT',
-  'LATINO',
-  'LATROBE',
-  'LAW',
-  'LAWYER',
-  'LB',
-  'LC',
-  'LDS',
-  'LEASE',
-  'LECLERC',
-  'LEFRAK',
-  'LEGAL',
-  'LEGO',
-  'LEXUS',
-  'LGBT',
-  'LI',
-  'LIAISON',
-  'LIDL',
-  'LIFE',
-  'LIFEINSURANCE',
-  'LIFESTYLE',
-  'LIGHTING',
-  'LIKE',
-  'LILLY',
-  'LIMITED',
-  'LIMO',
-  'LINCOLN',
-  'LINDE',
-  'LINK',
-  'LIPSY',
-  'LIVE',
-  'LIVING',
-  'LIXIL',
-  'LK',
-  'LLC',
-  'LOAN',
-  'LOANS',
-  'LOCKER',
-  'LOCUS',
-  'LOFT',
-  'LOL',
-  'LONDON',
-  'LOTTE',
-  'LOTTO',
-  'LOVE',
-  'LPL',
-  'LPLFINANCIAL',
-  'LR',
-  'LS',
-  'LT',
-  'LTD',
-  'LTDA',
-  'LU',
-  'LUNDBECK',
-  'LUPIN',
-  'LUXE',
-  'LUXURY',
-  'LV',
-  'LY',
-  'MA',
-  'MACYS',
-  'MADRID',
-  'MAIF',
-  'MAISON',
-  'MAKEUP',
-  'MAN',
-  'MANAGEMENT',
-  'MANGO',
-  'MAP',
-  'MARKET',
-  'MARKETING',
-  'MARKETS',
-  'MARRIOTT',
-  'MARSHALLS',
-  'MASERATI',
-  'MATTEL',
-  'MBA',
-  'MC',
-  'MCKINSEY',
-  'MD',
-  'ME',
-  'MED',
-  'MEDIA',
-  'MEET',
-  'MELBOURNE',
-  'MEME',
-  'MEMORIAL',
-  'MEN',
-  'MENU',
-  'MERCKMSD',
-  'METLIFE',
-  'MG',
-  'MH',
-  'MIAMI',
-  'MICROSOFT',
-  'MIL',
-  'MINI',
-  'MINT',
-  'MIT',
-  'MITSUBISHI',
-  'MK',
-  'ML',
-  'MLB',
-  'MLS',
-  'MM',
-  'MMA',
-  'MN',
-  'MO',
-  'MOBI',
-  'MOBILE',
-  'MODA',
-  'MOE',
-  'MOI',
-  'MOM',
-  'MONASH',
-  'MONEY',
-  'MONSTER',
-  'MOPAR',
-  'MORMON',
-  'MORTGAGE',
-  'MOSCOW',
-  'MOTO',
-  'MOTORCYCLES',
-  'MOV',
-  'MOVIE',
-  'MOVISTAR',
-  'MP',
-  'MQ',
-  'MR',
-  'MS',
-  'MSD',
-  'MT',
-  'MTN',
-  'MTR',
-  'MU',
-  'MUSEUM',
-  'MUTUAL',
-  'MV',
-  'MW',
-  'MX',
-  'MY',
-  'MZ',
-  'NA',
-  'NAB',
-  'NADEX',
-  'NAGOYA',
-  'NAME',
-  'NATIONWIDE',
-  'NATURA',
-  'NAVY',
-  'NBA',
-  'NC',
-  'NE',
-  'NEC',
-  'NET',
-  'NETBANK',
-  'NETFLIX',
-  'NETWORK',
-  'NEUSTAR',
-  'NEW',
-  'NEWHOLLAND',
-  'NEWS',
-  'NEXT',
-  'NEXTDIRECT',
-  'NEXUS',
-  'NF',
-  'NFL',
-  'NG',
-  'NGO',
-  'NHK',
-  'NI',
-  'NICO',
-  'NIKE',
-  'NIKON',
-  'NINJA',
-  'NISSAN',
-  'NISSAY',
-  'NL',
-  'NO',
-  'NOKIA',
-  'NORTHWESTERNMUTUAL',
-  'NORTON',
-  'NOW',
-  'NOWRUZ',
-  'NOWTV',
-  'NP',
-  'NR',
-  'NRA',
-  'NRW',
-  'NTT',
-  'NU',
-  'NYC',
-  'NZ',
-  'OBI',
-  'OBSERVER',
-  'OFF',
-  'OFFICE',
-  'OKINAWA',
-  'OLAYAN',
-  'OLAYANGROUP',
-  'OLDNAVY',
-  'OLLO',
-  'OM',
-  'OMEGA',
-  'ONE',
-  'ONG',
-  'ONL',
-  'ONLINE',
-  'ONYOURSIDE',
-  'OOO',
-  'OPEN',
-  'ORACLE',
-  'ORANGE',
-  'ORG',
-  'ORGANIC',
-  'ORIGINS',
-  'OSAKA',
-  'OTSUKA',
-  'OTT',
-  'OVH',
-  'PA',
-  'PAGE',
-  'PANASONIC',
-  'PARIS',
-  'PARS',
-  'PARTNERS',
-  'PARTS',
-  'PARTY',
-  'PASSAGENS',
-  'PAY',
-  'PCCW',
-  'PE',
-  'PET',
-  'PF',
-  'PFIZER',
-  'PG',
-  'PH',
-  'PHARMACY',
-  'PHD',
-  'PHILIPS',
-  'PHONE',
-  'PHOTO',
-  'PHOTOGRAPHY',
-  'PHOTOS',
-  'PHYSIO',
-  'PIAGET',
-  'PICS',
-  'PICTET',
-  'PICTURES',
-  'PID',
-  'PIN',
-  'PING',
-  'PINK',
-  'PIONEER',
-  'PIZZA',
-  'PK',
-  'PL',
-  'PLACE',
-  'PLAY',
-  'PLAYSTATION',
-  'PLUMBING',
-  'PLUS',
-  'PM',
-  'PN',
-  'PNC',
-  'POHL',
-  'POKER',
-  'POLITIE',
-  'PORN',
-  'POST',
-  'PR',
-  'PRAMERICA',
-  'PRAXI',
-  'PRESS',
-  'PRIME',
-  'PRO',
-  'PROD',
-  'PRODUCTIONS',
-  'PROF',
-  'PROGRESSIVE',
-  'PROMO',
-  'PROPERTIES',
-  'PROPERTY',
-  'PROTECTION',
-  'PRU',
-  'PRUDENTIAL',
-  'PS',
-  'PT',
-  'PUB',
-  'PW',
-  'PWC',
-  'PY',
-  'QA',
-  'QPON',
-  'QUEBEC',
-  'QUEST',
-  'QVC',
-  'RACING',
-  'RADIO',
-  'RAID',
-  'RE',
-  'READ',
-  'REALESTATE',
-  'REALTOR',
-  'REALTY',
-  'RECIPES',
-  'RED',
-  'REDSTONE',
-  'REDUMBRELLA',
-  'REHAB',
-  'REISE',
-  'REISEN',
-  'REIT',
-  'RELIANCE',
-  'REN',
-  'RENT',
-  'RENTALS',
-  'REPAIR',
-  'REPORT',
-  'REPUBLICAN',
-  'REST',
-  'RESTAURANT',
-  'REVIEW',
-  'REVIEWS',
-  'REXROTH',
-  'RICH',
-  'RICHARDLI',
-  'RICOH',
-  'RIGHTATHOME',
-  'RIL',
-  'RIO',
-  'RIP',
-  'RMIT',
-  'RO',
-  'ROCHER',
-  'ROCKS',
-  'RODEO',
-  'ROGERS',
-  'ROOM',
-  'RS',
-  'RSVP',
-  'RU',
-  'RUGBY',
-  'RUHR',
-  'RUN',
-  'RW',
-  'RWE',
-  'RYUKYU',
-  'SA',
-  'SAARLAND',
-  'SAFE',
-  'SAFETY',
-  'SAKURA',
-  'SALE',
-  'SALON',
-  'SAMSCLUB',
-  'SAMSUNG',
-  'SANDVIK',
-  'SANDVIKCOROMANT',
-  'SANOFI',
-  'SAP',
-  'SARL',
-  'SAS',
-  'SAVE',
-  'SAXO',
-  'SB',
-  'SBI',
-  'SBS',
-  'SC',
-  'SCA',
-  'SCB',
-  'SCHAEFFLER',
-  'SCHMIDT',
-  'SCHOLARSHIPS',
-  'SCHOOL',
-  'SCHULE',
-  'SCHWARZ',
-  'SCIENCE',
-  'SCJOHNSON',
-  'SCOR',
-  'SCOT',
-  'SD',
-  'SE',
-  'SEARCH',
-  'SEAT',
-  'SECURE',
-  'SECURITY',
-  'SEEK',
-  'SELECT',
-  'SENER',
-  'SERVICES',
-  'SES',
-  'SEVEN',
-  'SEW',
-  'SEX',
-  'SEXY',
-  'SFR',
-  'SG',
-  'SH',
-  'SHANGRILA',
-  'SHARP',
-  'SHAW',
-  'SHELL',
-  'SHIA',
-  'SHIKSHA',
-  'SHOES',
-  'SHOP',
-  'SHOPPING',
-  'SHOUJI',
-  'SHOW',
-  'SHOWTIME',
-  'SHRIRAM',
-  'SI',
-  'SILK',
-  'SINA',
-  'SINGLES',
-  'SITE',
-  'SJ',
-  'SK',
-  'SKI',
-  'SKIN',
-  'SKY',
-  'SKYPE',
-  'SL',
-  'SLING',
-  'SM',
-  'SMART',
-  'SMILE',
-  'SN',
-  'SNCF',
-  'SO',
-  'SOCCER',
-  'SOCIAL',
-  'SOFTBANK',
-  'SOFTWARE',
-  'SOHU',
-  'SOLAR',
-  'SOLUTIONS',
-  'SONG',
-  'SONY',
-  'SOY',
-  'SPACE',
-  'SPORT',
-  'SPOT',
-  'SPREADBETTING',
-  'SR',
-  'SRL',
-  'SRT',
-  'SS',
-  'ST',
-  'STADA',
-  'STAPLES',
-  'STAR',
-  'STATEBANK',
-  'STATEFARM',
-  'STC',
-  'STCGROUP',
-  'STOCKHOLM',
-  'STORAGE',
-  'STORE',
-  'STREAM',
-  'STUDIO',
-  'STUDY',
-  'STYLE',
-  'SU',
-  'SUCKS',
-  'SUPPLIES',
-  'SUPPLY',
-  'SUPPORT',
-  'SURF',
-  'SURGERY',
-  'SUZUKI',
-  'SV',
-  'SWATCH',
-  'SWIFTCOVER',
-  'SWISS',
-  'SX',
-  'SY',
-  'SYDNEY',
-  'SYMANTEC',
-  'SYSTEMS',
-  'SZ',
-  'TAB',
-  'TAIPEI',
-  'TALK',
-  'TAOBAO',
-  'TARGET',
-  'TATAMOTORS',
-  'TATAR',
-  'TATTOO',
-  'TAX',
-  'TAXI',
-  'TC',
-  'TCI',
-  'TD',
-  'TDK',
-  'TEAM',
-  'TECH',
-  'TECHNOLOGY',
-  'TEL',
-  'TELEFONICA',
-  'TEMASEK',
-  'TENNIS',
-  'TEVA',
-  'TF',
-  'TG',
-  'TH',
-  'THD',
-  'THEATER',
-  'THEATRE',
-  'TIAA',
-  'TICKETS',
-  'TIENDA',
-  'TIFFANY',
-  'TIPS',
-  'TIRES',
-  'TIROL',
-  'TJ',
-  'TJMAXX',
-  'TJX',
-  'TK',
-  'TKMAXX',
-  'TL',
-  'TM',
-  'TMALL',
-  'TN',
-  'TO',
-  'TODAY',
-  'TOKYO',
-  'TOOLS',
-  'TOP',
-  'TORAY',
-  'TOSHIBA',
-  'TOTAL',
-  'TOURS',
-  'TOWN',
-  'TOYOTA',
-  'TOYS',
-  'TR',
-  'TRADE',
-  'TRADING',
-  'TRAINING',
-  'TRAVEL',
-  'TRAVELCHANNEL',
-  'TRAVELERS',
-  'TRAVELERSINSURANCE',
-  'TRUST',
-  'TRV',
-  'TT',
-  'TUBE',
-  'TUI',
-  'TUNES',
-  'TUSHU',
-  'TV',
-  'TVS',
-  'TW',
-  'TZ',
-  'UA',
-  'UBANK',
-  'UBS',
-  'UCONNECT',
-  'UG',
-  'UK',
-  'UNICOM',
-  'UNIVERSITY',
-  'UNO',
-  'UOL',
-  'UPS',
-  'US',
-  'UY',
-  'UZ',
-  'VA',
-  'VACATIONS',
-  'VANA',
-  'VANGUARD',
-  'VC',
-  'VE',
-  'VEGAS',
-  'VENTURES',
-  'VERISIGN',
-  'VERSICHERUNG',
-  'VET',
-  'VG',
-  'VI',
-  'VIAJES',
-  'VIDEO',
-  'VIG',
-  'VIKING',
-  'VILLAS',
-  'VIN',
-  'VIP',
-  'VIRGIN',
-  'VISA',
-  'VISION',
-  'VISTAPRINT',
-  'VIVA',
-  'VIVO',
-  'VLAANDEREN',
-  'VN',
-  'VODKA',
-  'VOLKSWAGEN',
-  'VOLVO',
-  'VOTE',
-  'VOTING',
-  'VOTO',
-  'VOYAGE',
-  'VU',
-  'VUELOS',
-  'WALES',
-  'WALMART',
-  'WALTER',
-  'WANG',
-  'WANGGOU',
-  'WARMAN',
-  'WATCH',
-  'WATCHES',
-  'WEATHER',
-  'WEATHERCHANNEL',
-  'WEBCAM',
-  'WEBER',
-  'WEBSITE',
-  'WED',
-  'WEDDING',
-  'WEIBO',
-  'WEIR',
-  'WF',
-  'WHOSWHO',
-  'WIEN',
-  'WIKI',
-  'WILLIAMHILL',
-  'WIN',
-  'WINDOWS',
-  'WINE',
-  'WINNERS',
-  'WME',
-  'WOLTERSKLUWER',
-  'WOODSIDE',
-  'WORK',
-  'WORKS',
-  'WORLD',
-  'WOW',
-  'WS',
-  'WTC',
-  'WTF',
-  'XBOX',
-  'XEROX',
-  'XFINITY',
-  'XIHUAN',
-  'XIN',
-  'XN--11B4C3D',
-  'XN--1CK2E1B',
-  'XN--1QQW23A',
-  'XN--2SCRJ9C',
-  'XN--30RR7Y',
-  'XN--3BST00M',
-  'XN--3DS443G',
-  'XN--3E0B707E',
-  'XN--3HCRJ9C',
-  'XN--3OQ18VL8PN36A',
-  'XN--3PXU8K',
-  'XN--42C2D9A',
-  'XN--45BR5CYL',
-  'XN--45BRJ9C',
-  'XN--45Q11C',
-  'XN--4GBRIM',
-  'XN--54B7FTA0CC',
-  'XN--55QW42G',
-  'XN--55QX5D',
-  'XN--5SU34J936BGSG',
-  'XN--5TZM5G',
-  'XN--6FRZ82G',
-  'XN--6QQ986B3XL',
-  'XN--80ADXHKS',
-  'XN--80AO21A',
-  'XN--80AQECDR1A',
-  'XN--80ASEHDB',
-  'XN--80ASWG',
-  'XN--8Y0A063A',
-  'XN--90A3AC',
-  'XN--90AE',
-  'XN--90AIS',
-  'XN--9DBQ2A',
-  'XN--9ET52U',
-  'XN--9KRT00A',
-  'XN--B4W605FERD',
-  'XN--BCK1B9A5DRE4C',
-  'XN--C1AVG',
-  'XN--C2BR7G',
-  'XN--CCK2B3B',
-  'XN--CG4BKI',
-  'XN--CLCHC0EA0B2G2A9GCD',
-  'XN--CZR694B',
-  'XN--CZRS0T',
-  'XN--CZRU2D',
-  'XN--D1ACJ3B',
-  'XN--D1ALF',
-  'XN--E1A4C',
-  'XN--ECKVDTC9D',
-  'XN--EFVY88H',
-  'XN--ESTV75G',
-  'XN--FCT429K',
-  'XN--FHBEI',
-  'XN--FIQ228C5HS',
-  'XN--FIQ64B',
-  'XN--FIQS8S',
-  'XN--FIQZ9S',
-  'XN--FJQ720A',
-  'XN--FLW351E',
-  'XN--FPCRJ9C3D',
-  'XN--FZC2C9E2C',
-  'XN--FZYS8D69UVGM',
-  'XN--G2XX48C',
-  'XN--GCKR3F0F',
-  'XN--GECRJ9C',
-  'XN--GK3AT1E',
-  'XN--H2BREG3EVE',
-  'XN--H2BRJ9C',
-  'XN--H2BRJ9C8C',
-  'XN--HXT814E',
-  'XN--I1B6B1A6A2E',
-  'XN--IMR513N',
-  'XN--IO0A7I',
-  'XN--J1AEF',
-  'XN--J1AMH',
-  'XN--J6W193G',
-  'XN--JLQ61U9W7B',
-  'XN--JVR189M',
-  'XN--KCRX77D1X4A',
-  'XN--KPRW13D',
-  'XN--KPRY57D',
-  'XN--KPU716F',
-  'XN--KPUT3I',
-  'XN--L1ACC',
-  'XN--LGBBAT1AD8J',
-  'XN--MGB9AWBF',
-  'XN--MGBA3A3EJT',
-  'XN--MGBA3A4F16A',
-  'XN--MGBA7C0BBN0A',
-  'XN--MGBAAKC7DVF',
-  'XN--MGBAAM7A8H',
-  'XN--MGBAB2BD',
-  'XN--MGBAH1A3HJKRD',
-  'XN--MGBAI9AZGQP6J',
-  'XN--MGBAYH7GPA',
-  'XN--MGBBH1A',
-  'XN--MGBBH1A71E',
-  'XN--MGBC0A9AZCG',
-  'XN--MGBCA7DZDO',
-  'XN--MGBERP4A5D4AR',
-  'XN--MGBGU82A',
-  'XN--MGBI4ECEXP',
-  'XN--MGBPL2FH',
-  'XN--MGBT3DHD',
-  'XN--MGBTX2B',
-  'XN--MGBX4CD0AB',
-  'XN--MIX891F',
-  'XN--MK1BU44C',
-  'XN--MXTQ1M',
-  'XN--NGBC5AZD',
-  'XN--NGBE9E0A',
-  'XN--NGBRX',
-  'XN--NODE',
-  'XN--NQV7F',
-  'XN--NQV7FS00EMA',
-  'XN--NYQY26A',
-  'XN--O3CW4H',
-  'XN--OGBPF8FL',
-  'XN--OTU796D',
-  'XN--P1ACF',
-  'XN--P1AI',
-  'XN--PBT977C',
-  'XN--PGBS0DH',
-  'XN--PSSY2U',
-  'XN--Q9JYB4C',
-  'XN--QCKA1PMC',
-  'XN--QXA6A',
-  'XN--QXAM',
-  'XN--RHQV96G',
-  'XN--ROVU88B',
-  'XN--RVC1E0AM3E',
-  'XN--S9BRJ9C',
-  'XN--SES554G',
-  'XN--T60B56A',
-  'XN--TCKWE',
-  'XN--TIQ49XQYJ',
-  'XN--UNUP4Y',
-  'XN--VERMGENSBERATER-CTB',
-  'XN--VERMGENSBERATUNG-PWB',
-  'XN--VHQUV',
-  'XN--VUQ861B',
-  'XN--W4R85EL8FHU5DNRA',
-  'XN--W4RS40L',
-  'XN--WGBH1C',
-  'XN--WGBL6A',
-  'XN--XHQ521B',
-  'XN--XKC2AL3HYE2A',
-  'XN--XKC2DL3A5EE0H',
-  'XN--Y9A3AQ',
-  'XN--YFRO4I67O',
-  'XN--YGBI2AMMX',
-  'XN--ZFR164B',
-  'XXX',
-  'XYZ',
-  'YACHTS',
-  'YAHOO',
-  'YAMAXUN',
-  'YANDEX',
-  'YE',
-  'YODOBASHI',
-  'YOGA',
-  'YOKOHAMA',
-  'YOU',
-  'YOUTUBE',
-  'YT',
-  'YUN',
-  'ZA',
-  'ZAPPOS',
-  'ZARA',
-  'ZERO',
-  'ZIP',
-  'ZM',
-  'ZONE',
-  'ZUERICH',
-  'ZW',
-];
diff --git a/bin/simple_browser/lib/src/widgets/error_page.dart b/bin/simple_browser/lib/src/widgets/error_page.dart
deleted file mode 100644
index c9c39a9..0000000
--- a/bin/simple_browser/lib/src/widgets/error_page.dart
+++ /dev/null
@@ -1,238 +0,0 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'dart:async' show Timer;
-import 'dart:math' show Random;
-import 'dart:ui' show lerpDouble;
-
-import 'package:flutter/material.dart';
-import 'package:flutter/services.dart' show RawKeyDownEvent, RawKeyEvent;
-
-const _kStartLength = 5;
-const _kMaxCrumbs = 20;
-const _kCrumbChanceToAppear = 0.05;
-const _kKeyLabelToDirection = <String, Coord>{
-  'w': Coord.up,
-  'a': Coord.left,
-  's': Coord.down,
-  'd': Coord.right,
-};
-const _kInitialCoords = <Coord>[
-  Coord(-2, 0),
-  Coord(-1, 0),
-  Coord(0, 0),
-  Coord(1, 0),
-  Coord(2, 0),
-];
-
-class ErrorPage extends StatefulWidget {
-  @override
-  _ErrorPageState createState() => _ErrorPageState();
-}
-
-class _ErrorPageState extends State<ErrorPage> {
-  final _focusNode = FocusNode();
-  final _direction = ValueNotifier<Coord?>(null);
-  final _coords = <Coord>[];
-  final _crumbCoords = <Coord>[];
-  int _length = _kStartLength;
-  Timer? _timer;
-  Size? _screenSize;
-  Offset? _screenCenter;
-  bool _lost = false;
-  final _random = Random();
-  double rnd() => _random.nextDouble();
-
-  @override
-  void initState() {
-    super.initState();
-    if (WidgetsBinding.instance != null) {
-      WidgetsBinding.instance!.addPostFrameCallback(
-          (_) => FocusScope.of(context).requestFocus(_focusNode));
-    }
-    _reset();
-  }
-
-  void _reset() {
-    _coords
-      ..clear()
-      ..addAll(_kInitialCoords);
-    _crumbCoords.clear();
-    _length = _kStartLength;
-    _lost = false;
-  }
-
-  void _startTimer() {
-    _timer?.cancel();
-    _timer = Timer.periodic(Duration(milliseconds: 250), _onTimer);
-  }
-
-  @override
-  void dispose() {
-    _timer?.cancel();
-    _focusNode.dispose();
-    super.dispose();
-  }
-
-  @override
-  Widget build(BuildContext context) => RawKeyboardListener(
-        focusNode: FocusNode(),
-        onKey: _onKey,
-        child: GestureDetector(
-          child: Container(
-            color: Colors.transparent,
-            child: LayoutBuilder(builder: (context, constraints) {
-              _screenSize = constraints.biggest;
-              _screenCenter = _screenSize!.center(Offset.zero);
-              return Stack(
-                children: [
-                  ..._coords.asMap().map(_buildBody).values.toList(),
-                  ..._crumbCoords.map(_buildCrumb).toList(),
-                  Opacity(
-                    opacity: 0,
-                    child: TextField(
-                      focusNode: _focusNode,
-                      autofocus: true,
-                    ),
-                  ),
-                ],
-              );
-            }),
-          ),
-          onTap: () => FocusScope.of(context).requestFocus(_focusNode),
-        ),
-      );
-
-  void _onKey(RawKeyEvent value) {
-    if (value.runtimeType == RawKeyDownEvent) {
-      if (_lost) {
-        _reset();
-      }
-
-      Coord? newDirection = _kKeyLabelToDirection[value.logicalKey.keyLabel];
-      if (newDirection != null) {
-        if (_coords[_coords.length - 2] - _coords.last != newDirection) {
-          _direction.value = newDirection;
-          _onTimer(null);
-          _startTimer();
-        }
-      }
-    }
-  }
-
-  String _textForIndex(int index) {
-    if (index == 0) {
-      return 'E';
-    } else if (index == _coords.length - 2) {
-      return 'O';
-    } else {
-      return 'R';
-    }
-  }
-
-  Widget _buildCrumb(Coord coord) =>
-      _buildSquare(squaresToScreen(coord), 'R', false);
-
-  MapEntry<int, Widget> _buildBody(int index, Coord coord) => MapEntry(
-      index, _buildSquare(squaresToScreen(coord), _textForIndex(index), true));
-
-  Widget _buildSquare(Offset offset, String string, bool invert) => Positioned(
-        left: offset.dx,
-        top: offset.dy,
-        child: Container(
-          width: 16,
-          height: 16,
-          color: invert ? Colors.black : null,
-          child: Text(
-            string,
-            textAlign: TextAlign.center,
-            style: invert ? TextStyle(color: Colors.white) : null,
-          ),
-        ),
-      );
-
-  Coord screenToSquares(Offset screen) => Coord(
-      ((screen.dx - _screenCenter!.dx) / 16).floor(),
-      ((screen.dy - _screenCenter!.dy) / 16).floor());
-  Offset squaresToScreen(Coord squares) =>
-      Offset((squares.x - 0.5), (squares.y - 0.5)) * 16 + _screenCenter!;
-
-  void _addCrumb() {
-    Coord newCrumb = screenToSquares(Offset(
-      lerpDouble(0, _screenSize!.width, rnd())!,
-      lerpDouble(0, _screenSize!.height, rnd())!,
-    ));
-
-    // add if coordinate is currently free
-    if (!_coords.contains(newCrumb) && !_crumbCoords.contains(newCrumb)) {
-      _crumbCoords.add(newCrumb);
-    }
-  }
-
-  void _onTimer(Timer? timer) {
-    if (_direction.value == null) {
-      return;
-    }
-
-    Coord newCoord = _coords.last + _direction.value!;
-
-    // lost: eating own tail
-    if (_coords.contains(newCoord)) {
-      _lost = true;
-      _timer?.cancel();
-      return;
-    }
-
-    // lost: leaving the screen
-    if (!(Offset.zero & _screenSize!).contains(squaresToScreen(newCoord))) {
-      _lost = true;
-      _timer?.cancel();
-      return;
-    }
-
-    setState(() {
-      _coords.add(newCoord);
-
-      if (_coords.length > _length) {
-        _coords.removeAt(0);
-      }
-
-      // yum
-      if (_crumbCoords.remove(newCoord)) {
-        _length++;
-      }
-
-      // more food
-      if (_crumbCoords.length < _kMaxCrumbs && rnd() < _kCrumbChanceToAppear) {
-        _addCrumb();
-      }
-    });
-  }
-}
-
-class Coord {
-  const Coord(this.x, this.y);
-  final int x;
-  final int y;
-
-  static const Coord up = Coord(0, -1);
-  static const Coord left = Coord(-1, 0);
-  static const Coord down = Coord(0, 1);
-  static const Coord right = Coord(1, 0);
-
-  Coord operator -(Coord other) => Coord(x - other.x, y - other.y);
-  Coord operator +(Coord other) => Coord(x + other.x, y + other.y);
-
-  @override
-  bool operator ==(dynamic other) {
-    if (other is! Coord) {
-      return false;
-    }
-    final Coord typedOther = other;
-    return x == typedOther.x && y == typedOther.y;
-  }
-
-  @override
-  int get hashCode => hashValues(x, y);
-}
diff --git a/bin/simple_browser/lib/src/widgets/history_buttons.dart b/bin/simple_browser/lib/src/widgets/history_buttons.dart
deleted file mode 100644
index 72f1240..0000000
--- a/bin/simple_browser/lib/src/widgets/history_buttons.dart
+++ /dev/null
@@ -1,80 +0,0 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'package:flutter/material.dart';
-import 'package:internationalization/strings.dart';
-
-import '../blocs/webpage_bloc.dart';
-import '../models/webpage_action.dart';
-
-const _kEnabledOpacity = 1.0;
-const _kDisabledOpacity = 0.54;
-const _kPadding = EdgeInsets.symmetric(horizontal: 4.0);
-
-class HistoryButtons extends StatelessWidget {
-  const HistoryButtons({required this.bloc});
-
-  final WebPageBloc bloc;
-
-  @override
-  Widget build(BuildContext context) {
-    return Row(
-      crossAxisAlignment: CrossAxisAlignment.stretch,
-      children: <Widget>[
-        AnimatedBuilder(
-            animation: bloc.backStateNotifier,
-            builder: (_, __) => _HistoryButton(
-                title: Strings.back.toUpperCase(),
-                valueKey: 'back',
-                onTap: () => bloc.request.add(GoBackAction()),
-                isEnabled: bloc.backState)),
-        SizedBox(width: 8.0),
-        AnimatedBuilder(
-            animation: bloc.forwardStateNotifier,
-            builder: (_, __) => _HistoryButton(
-                title: Strings.forward.toUpperCase(),
-                valueKey: 'forward',
-                onTap: () => bloc.request.add(GoForwardAction()),
-                isEnabled: bloc.forwardState)),
-        SizedBox(width: 8.0),
-        AnimatedBuilder(
-            animation: bloc.urlNotifier,
-            builder: (_, __) => _HistoryButton(
-                title: Strings.refresh.toUpperCase(),
-                valueKey: 'refresh',
-                onTap: () => bloc.request.add(RefreshAction()),
-                isEnabled: bloc.pageType == PageType.normal)),
-      ],
-    );
-  }
-}
-
-class _HistoryButton extends StatelessWidget {
-  const _HistoryButton({
-    required this.title,
-    required this.valueKey,
-    required this.onTap,
-    required this.isEnabled,
-  });
-
-  final String title;
-  final String valueKey;
-  final VoidCallback onTap;
-  final bool isEnabled;
-
-  @override
-  Widget build(BuildContext context) => GestureDetector(
-        key: ValueKey(valueKey),
-        onTap: isEnabled ? onTap : null,
-        child: Padding(
-          padding: _kPadding,
-          child: Center(
-            child: Opacity(
-              opacity: isEnabled ? _kEnabledOpacity : _kDisabledOpacity,
-              child: Text(title),
-            ),
-          ),
-        ),
-      );
-}
diff --git a/bin/simple_browser/lib/src/widgets/navigation_bar.dart b/bin/simple_browser/lib/src/widgets/navigation_bar.dart
deleted file mode 100644
index af2ef9e..0000000
--- a/bin/simple_browser/lib/src/widgets/navigation_bar.dart
+++ /dev/null
@@ -1,147 +0,0 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'package:flutter/material.dart';
-import '../blocs/webpage_bloc.dart';
-import 'history_buttons.dart';
-import 'navigation_field.dart';
-
-// TODO(fxb/45264): Make the common factors as part of Ermine central styles.
-const _kNavbarHeight = 24.0;
-const _kIconSize = 14.0;
-
-enum _LayoutId { historyButtons, url, addTabButton }
-
-class BrowserNavigationBar extends StatelessWidget {
-  final WebPageBloc? bloc;
-  final VoidCallback newTab;
-  final FocusNode _fieldFocus;
-
-  const BrowserNavigationBar({
-    required this.bloc,
-    required this.newTab,
-    required FocusNode fieldFocus,
-  }) : _fieldFocus = fieldFocus;
-
-  @override
-  Widget build(BuildContext context) => Material(
-        child: SizedBox(
-          height: _kNavbarHeight,
-          child: Stack(
-            children: <Widget>[
-              Positioned.fill(child: _buildWidgets(context)),
-              if (bloc != null)
-                Align(
-                  alignment: Alignment.bottomCenter,
-                  child: _buildLoadingIndicator(),
-                ),
-            ],
-          ),
-        ),
-      );
-
-  Widget _buildLoadingIndicator() {
-    return AnimatedBuilder(
-      animation: bloc!.isLoadedStateNotifier,
-      builder: (context, snapshot) => bloc!.isLoadedState
-          ? Offstage()
-          : SizedBox(
-              width: double.infinity,
-              height: 4.0,
-              child: LinearProgressIndicator(
-                color: Theme.of(context).colorScheme.secondary,
-                backgroundColor: Colors.transparent,
-              ),
-            ),
-    );
-  }
-
-  Widget _buildWidgets(BuildContext context) {
-    return CustomMultiChildLayout(
-      delegate: _LayoutDelegate(),
-      children: [
-        LayoutId(
-          id: _LayoutId.historyButtons,
-          child: bloc != null ? HistoryButtons(bloc: bloc!) : Container(),
-        ),
-        LayoutId(
-          id: _LayoutId.url,
-          child: bloc != null
-              ? NavigationField(bloc: bloc!, focus: _fieldFocus)
-              : Container(),
-        ),
-        LayoutId(
-          id: _LayoutId.addTabButton,
-          child: _buildNewTabButton(context),
-        ),
-      ],
-    );
-  }
-
-  Widget _buildNewTabButton(BuildContext context) {
-    return GestureDetector(
-      onTap: newTab,
-      child: Align(
-        alignment: Alignment.centerRight,
-        child: AspectRatio(
-          aspectRatio: 1.0,
-          child: Padding(
-            padding: const EdgeInsets.all(1.0),
-            child: Container(
-              color: Theme.of(context).colorScheme.secondary,
-              alignment: Alignment.center,
-              child: Icon(
-                Icons.add,
-                key: Key('new_tab'),
-                color: Theme.of(context).colorScheme.primary,
-                size: _kIconSize,
-              ),
-            ),
-          ),
-        ),
-      ),
-    );
-  }
-}
-
-class _LayoutDelegate extends MultiChildLayoutDelegate {
-  @override
-  void performLayout(Size size) {
-    final historyButtonsSize = layoutChild(
-      _LayoutId.historyButtons,
-      BoxConstraints.tightFor(height: size.height),
-    );
-    positionChild(_LayoutId.historyButtons, Offset.zero);
-    final newTabButtonSize = layoutChild(
-      _LayoutId.addTabButton,
-      BoxConstraints(
-        minHeight: size.height,
-        maxHeight: size.height,
-        minWidth: historyButtonsSize.width,
-      ),
-    );
-    positionChild(
-      _LayoutId.addTabButton,
-      size.topRight(-newTabButtonSize.topRight(Offset.zero)),
-    );
-
-    final urlSize = layoutChild(
-      _LayoutId.url,
-      BoxConstraints.tightFor(
-        width: (size.width - historyButtonsSize.width - newTabButtonSize.width)
-            .clamp(0.0, size.width),
-      ),
-    );
-    positionChild(
-      _LayoutId.url,
-      Offset(
-        historyButtonsSize.width,
-        (size.height - urlSize.height) * 0.5,
-      ),
-    );
-  }
-
-  @override
-  bool shouldRelayout(MultiChildLayoutDelegate oldDelegate) => false;
-}
diff --git a/bin/simple_browser/lib/src/widgets/navigation_field.dart b/bin/simple_browser/lib/src/widgets/navigation_field.dart
deleted file mode 100644
index 07bd38f..0000000
--- a/bin/simple_browser/lib/src/widgets/navigation_field.dart
+++ /dev/null
@@ -1,100 +0,0 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'package:flutter/material.dart';
-import 'package:internationalization/strings.dart';
-
-import '../blocs/webpage_bloc.dart';
-import '../models/webpage_action.dart';
-
-class NavigationField extends StatefulWidget {
-  const NavigationField({required this.bloc, required this.focus});
-  final WebPageBloc bloc;
-  final FocusNode focus;
-
-  @override
-  _NavigationFieldState createState() => _NavigationFieldState();
-}
-
-class _NavigationFieldState extends State<NavigationField> {
-  final _controller = TextEditingController();
-
-  @override
-  void initState() {
-    super.initState();
-    widget.focus.addListener(_onFocusChange);
-    _setupBloc(null, widget);
-  }
-
-  @override
-  void dispose() {
-    _setupBloc(widget, null);
-    _controller.dispose();
-    widget.focus
-      ..removeListener(_onFocusChange)
-      ..dispose();
-    super.dispose();
-  }
-
-  @override
-  void didUpdateWidget(NavigationField oldWidget) {
-    super.didUpdateWidget(oldWidget);
-    _setupBloc(oldWidget, widget);
-    _updateFocus();
-  }
-
-  void _setupBloc(NavigationField? oldWidget, NavigationField? newWidget) {
-    if (oldWidget?.bloc != newWidget?.bloc) {
-      oldWidget?.bloc.urlNotifier.removeListener(_onUrlChanged);
-      widget.bloc.urlNotifier.addListener(_onUrlChanged);
-      if (newWidget != null) {
-        _controller.text = newWidget.bloc.url;
-      }
-    }
-  }
-
-  void _updateFocus() {
-    if (_controller.text.isEmpty) {
-      FocusScope.of(context).requestFocus(widget.focus);
-    } else {
-      widget.focus.unfocus();
-    }
-  }
-
-  void _onFocusChange() {
-    if (widget.focus.hasFocus) {
-      _controller.selection =
-          TextSelection(baseOffset: 0, extentOffset: _controller.text.length);
-    }
-  }
-
-  void _onUrlChanged() {
-    _controller.text = widget.bloc.url;
-    _updateFocus();
-  }
-
-  @override
-  Widget build(BuildContext context) => TextField(
-        focusNode: widget.focus,
-        autofocus: _controller.text.isEmpty,
-        controller: _controller,
-        cursorWidth: 8,
-        cursorRadius: Radius.zero,
-        cursorColor: Colors.black,
-        enableInteractiveSelection: true,
-        textAlign: TextAlign.center,
-        keyboardType: TextInputType.url,
-        decoration: InputDecoration(
-          contentPadding: EdgeInsets.zero,
-          // In general, do not use space characters to move graphical elements
-          // around, or you are going to have a bad time. :)
-          hintText: '     ${Strings.search.toUpperCase()}',
-          border: InputBorder.none,
-          isDense: true,
-        ),
-        onSubmitted: (value) =>
-            widget.bloc.request.add(NavigateToAction(url: value)),
-        textInputAction: TextInputAction.go,
-      );
-}
diff --git a/bin/simple_browser/lib/src/widgets/tabs_widget.dart b/bin/simple_browser/lib/src/widgets/tabs_widget.dart
deleted file mode 100644
index 43c950e..0000000
--- a/bin/simple_browser/lib/src/widgets/tabs_widget.dart
+++ /dev/null
@@ -1,733 +0,0 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'package:flutter/material.dart';
-import 'package:internationalization/strings.dart';
-import '../blocs/tabs_bloc.dart';
-import '../blocs/webpage_bloc.dart';
-import '../models/tabs_action.dart';
-
-// TODO(fxb/45264): Make the common factors as part of Ermine central styles.
-const _kTabBarHeight = 24.0;
-const _kMinTabWidth = 120.0;
-const _kBorderWidth = 1.0;
-const _kTabPadding = EdgeInsets.symmetric(horizontal: _kTabBarHeight);
-const _kScrollToMargin = _kMinTabWidth / 3;
-const _kScrollAnimationDuration = 300;
-const _kAutoScrollOffset = 5.0;
-const _kIconSize = 14.0;
-
-enum _ScrollDirection { left, right }
-
-@visibleForTesting
-double get kTabBarHeight => _kTabBarHeight;
-
-@visibleForTesting
-double get kMinTabWidth => _kMinTabWidth;
-
-@visibleForTesting
-double get kBorderWidth => _kBorderWidth;
-
-@visibleForTesting
-double get kScrollToMargin => _kScrollToMargin;
-
-/// The list of currently opened tabs in the browser.
-///
-/// Builds different widget trees for the tab list depending on the selected tab
-/// and the number of tabs.
-/// Handles tab rearrangement and list scroll events, and is also involved in drawing
-/// tab borders as they are affected by the tab rearrangement action.
-class TabsWidget extends StatefulWidget {
-  final TabsBloc bloc;
-  const TabsWidget({required this.bloc});
-
-  @override
-  _TabsWidgetState createState() => _TabsWidgetState();
-}
-
-class _TabsWidgetState extends State<TabsWidget>
-    with TickerProviderStateMixin<TabsWidget> {
-  double _tabListWidth = 0.0;
-  double _tabWidth = 0.0;
-  double _dragCursorOffset = 0.0;
-  final _currentTabX = ValueNotifier<double>(0.0);
-
-  int _ghostIndex = 0;
-  int _dragStartIndex = 0;
-  late bool _isDragging;
-  late bool _isAnimating;
-
-  static const Duration _reorderAnimationDuration = Duration(milliseconds: 200);
-  late AnimationController _ghostController;
-  late AnimationController _leftNewGhostController;
-  late AnimationController _rightNewGhostController;
-
-  final _scrollController = ScrollController();
-  final _leftScrollButton = _ScrollButton(_ScrollDirection.left);
-  final _rightScrollButton = _ScrollButton(_ScrollDirection.right);
-
-  late ThemeData _browserTheme;
-
-  @override
-  void initState() {
-    super.initState();
-    _isDragging = false;
-    _isAnimating = false;
-
-    _ghostController =
-        AnimationController(vsync: this, duration: _reorderAnimationDuration);
-    _leftNewGhostController =
-        AnimationController(vsync: this, duration: _reorderAnimationDuration);
-    _rightNewGhostController =
-        AnimationController(vsync: this, duration: _reorderAnimationDuration);
-
-    _ghostController.value = 1.0;
-    _leftNewGhostController.value = 0.0;
-    _rightNewGhostController.value = 0.0;
-
-    _currentTabX.addListener(_onCurrentTabXChanged);
-    _leftNewGhostController.addStatusListener(_onLeftNewGhostControllerChanged);
-    _rightNewGhostController
-        .addStatusListener(_onRightNewGhostControllerChanged);
-    _setupBloc(null, widget);
-  }
-
-  @override
-  void dispose() {
-    _setupBloc(widget, null);
-    _ghostController.dispose();
-    _leftNewGhostController
-      ..removeStatusListener(_onLeftNewGhostControllerChanged)
-      ..dispose();
-    _rightNewGhostController
-      ..removeStatusListener(_onRightNewGhostControllerChanged)
-      ..dispose();
-    _scrollController.dispose();
-    super.dispose();
-  }
-
-  @override
-  void didUpdateWidget(TabsWidget oldWidget) {
-    super.didUpdateWidget(oldWidget);
-    _setupBloc(oldWidget, widget);
-  }
-
-  void _setupBloc(TabsWidget? oldWidget, TabsWidget? newWidget) {
-    if (oldWidget?.bloc != newWidget?.bloc) {
-      oldWidget?.bloc.currentTabNotifier.removeListener(_onCurrentTabChanged);
-      widget.bloc.currentTabNotifier.addListener(_onCurrentTabChanged);
-      oldWidget?.bloc.tabsNotifier.removeListener(_onTabsChanged);
-      widget.bloc.tabsNotifier.addListener(_onTabsChanged);
-    }
-  }
-
-  @override
-  Widget build(BuildContext context) {
-    _tabListWidth = MediaQuery.of(context).size.width;
-    _browserTheme = Theme.of(context);
-
-    return AnimatedBuilder(
-        animation: Listenable.merge(
-            [widget.bloc.tabsNotifier, widget.bloc.currentTabNotifier]),
-        builder: (_, __) {
-          if (widget.bloc.tabs.length > 1) {
-            _setVariableTabWidth();
-            return Container(
-              height: _kTabBarHeight,
-              decoration: BoxDecoration(
-                color: _browserTheme.colorScheme.primary,
-                border: Border(
-                  top: BorderSide(
-                    color: _browserTheme.colorScheme.secondary,
-                    width: _kBorderWidth,
-                  ),
-                  bottom: BorderSide(
-                    color: _browserTheme.colorScheme.secondary,
-                    width: _kBorderWidth,
-                  ),
-                ),
-              ),
-              child: LayoutBuilder(
-                builder: (context, constraints) => _tabWidth > _kMinTabWidth
-                    ? _buildTabStacks()
-                    : _buildScrollableTabListWithButtons(),
-              ),
-            );
-          }
-          return Offstage();
-        });
-  }
-
-  void _setVariableTabWidth() {
-    _tabWidth = (_tabListWidth / widget.bloc.tabs.length)
-        .clamp(_kMinTabWidth, _tabListWidth / 2);
-    if (!_isDragging) {
-      _currentTabX.value = _tabWidth * widget.bloc.currentTabIdx!;
-    }
-  }
-
-  // BUILDERS
-
-  Widget _buildScrollableTabListWithButtons() => Row(
-        children: <Widget>[
-          _buildScrollButton(_leftScrollButton),
-          Expanded(child: _buildScrollableTabList()),
-          _buildScrollButton(_rightScrollButton),
-        ],
-      );
-
-  Widget _buildScrollableTabList() => NotificationListener<ScrollNotification>(
-        onNotification: (scrollNotification) {
-          if (scrollNotification is ScrollEndNotification) {
-            _onScrollEnd(scrollNotification.metrics);
-          }
-          return true;
-        },
-        child: SingleChildScrollView(
-          controller: _scrollController,
-          scrollDirection: Axis.horizontal,
-          physics: NeverScrollableScrollPhysics(),
-          child: _buildTabStacks(),
-        ),
-      );
-
-  Widget _buildScrollButton(_ScrollButton button) => GestureDetector(
-        onTap: () => _onScrollButtonTap(button),
-        child: Container(
-          width: _kTabBarHeight,
-          height: _kTabBarHeight,
-          color: _browserTheme.colorScheme.secondaryVariant,
-          child: Center(
-            child: AnimatedBuilder(
-              animation: button.isEnabled,
-              builder: (_, __) => Icon(
-                button.icon,
-                color: button.isEnabled.value
-                    ? _browserTheme.colorScheme.primary
-                    : _browserTheme.colorScheme.primary.withOpacity(0.2),
-                size: _kIconSize,
-              ),
-            ),
-          ),
-        ),
-      );
-
-  Widget _buildTabStacks() => Stack(
-        children: <Widget>[
-          Row(
-            children: List.generate(widget.bloc.tabs.length, (index) {
-              return _wrapWithGestureDetector(
-                index,
-                _buildUnselectedTab(index),
-              );
-            }),
-          ),
-          AnimatedBuilder(
-            animation: _currentTabX,
-            builder: (_, __) => Positioned(
-              left: _currentTabX.value,
-              child: _wrapWithGestureDetector(
-                widget.bloc.currentTabIdx!,
-                _buildTabWithBorder(widget.bloc.currentTabIdx!),
-              ),
-            ),
-          ),
-        ],
-      );
-
-  // Makes a tab to be rearrangeable and selectable.
-  Widget _wrapWithGestureDetector(int index, Widget child) => GestureDetector(
-        onHorizontalDragStart: (DragStartDetails details) =>
-            _onDragStart(index, details),
-        onHorizontalDragUpdate: _onDragUpdate,
-        onHorizontalDragEnd: _onDragEnd,
-        child: child,
-      );
-
-  Widget _buildUnselectedTab(int index) {
-    final spacing = Container(
-      width: _tabWidth,
-      height: _kTabBarHeight,
-      decoration: BoxDecoration(
-        border: _buildBorder(index != 0),
-      ),
-    );
-
-    if (index == _ghostIndex) {
-      return _buildGhostTab(_ghostController, spacing);
-    }
-
-    int actualIndex = index;
-
-    // Shifts the tabs located between the moving tab's original and current positions
-    //  to the right if the it is moving to the left.
-    if (_ghostIndex < widget.bloc.currentTabIdx!) {
-      //
-      if (index > _ghostIndex && index <= widget.bloc.currentTabIdx!) {
-        actualIndex = index - 1;
-      }
-    }
-    // Shifts the tabs located between the moving tab's original and current positions
-    // to the left if it is moving to the right.
-    else if (_ghostIndex > widget.bloc.currentTabIdx!) {
-      if (index < _ghostIndex && index >= widget.bloc.currentTabIdx!) {
-        actualIndex = index + 1;
-      }
-    }
-
-    final child = _buildTabWithBorder(actualIndex, renderingIndex: index);
-
-    // Inserts a potential empty space to the left of the tab which is currently left
-    // to the moving tab.
-    if (index == _ghostIndex - 1) {
-      return Row(
-        children: [
-          _buildGhostTab(_leftNewGhostController, spacing),
-          child,
-        ],
-      );
-    }
-    // Inserts a potential empty space to the right of the tab which is currently right
-    // to the moving tab.
-    else if (index == _ghostIndex + 1) {
-      return Row(
-        children: [
-          child,
-          _buildGhostTab(_rightNewGhostController, spacing),
-        ],
-      );
-    }
-
-    return child;
-  }
-
-  Widget _buildTabWithBorder(int index, {int? renderingIndex}) {
-    renderingIndex ??= index;
-
-    return Container(
-      key: Key('tab'),
-      width: _tabWidth,
-      height: _kTabBarHeight,
-      decoration: BoxDecoration(
-        color: (index == widget.bloc.currentTabIdx)
-            ? _browserTheme.colorScheme.secondary
-            : _browserTheme.colorScheme.primary,
-        border: _buildBorder(index != widget.bloc.currentTabIdx &&
-            !((!_isAnimating) && renderingIndex == 0) &&
-            !(_isAnimating &&
-                (renderingIndex != _ghostIndex - 1 && renderingIndex == 0))),
-      ),
-      child: _buildTab(widget.bloc.tabs[index]),
-    );
-  }
-
-  // Creates an empty space for the selected tab on the unselected tab widget list.
-  Widget _buildGhostTab(
-          AnimationController animationController, Widget child) =>
-      SizeTransition(
-        sizeFactor: animationController,
-        axis: Axis.horizontal,
-        axisAlignment: -1.0,
-        child: child,
-      );
-
-  Border _buildBorder(bool hasBorder) => Border(
-        left: BorderSide(
-          color: hasBorder
-              ? _browserTheme.colorScheme.secondary
-              : Colors.transparent,
-          width: _kBorderWidth,
-        ),
-      );
-
-  Widget _buildTab(WebPageBloc tab) => _TabWidget(
-        bloc: tab,
-        selected: tab == widget.bloc.currentTab,
-        onSelect: () => widget.bloc.request.add(FocusTabAction(tab: tab)),
-        onClose: () => widget.bloc.request.add(RemoveTabAction(tab: tab)),
-      );
-
-  // EVENT HANDLERS
-
-  void _onTabsChanged() {
-    _syncGhost();
-  }
-
-  void _onCurrentTabChanged() {
-    _syncGhost();
-
-    if (_scrollController.hasClients) {
-      final viewportWidth = _scrollController.position.viewportDimension;
-
-      final offsetForLeftEdge = _currentTabX.value - _kScrollToMargin;
-      final offsetForRightEdge =
-          _currentTabX.value - viewportWidth + _kMinTabWidth + _kScrollToMargin;
-
-      double? newOffset;
-
-      if (_scrollController.offset > offsetForLeftEdge) {
-        newOffset = offsetForLeftEdge;
-      } else if (_scrollController.offset < offsetForRightEdge) {
-        newOffset = offsetForRightEdge;
-      }
-
-      if (newOffset != null) {
-        _scrollController.animateTo(
-          newOffset,
-          duration: Duration(milliseconds: _kScrollAnimationDuration),
-          curve: Curves.ease,
-        );
-      }
-    }
-  }
-
-  void _syncGhost() {
-    final currentIdx = widget.bloc.currentTabIdx;
-    if (currentIdx == null) {
-      return;
-    }
-    _ghostIndex = currentIdx;
-    _currentTabX.value = _tabWidth * _ghostIndex;
-  }
-
-  // Remembers the current values of properties such as the dragging target tab's
-  // position and the scroll controller's offset when dragging starts.
-  void _onDragStart(int index, DragStartDetails details) {
-    if (index != widget.bloc.currentTabIdx) {
-      widget.bloc.request.add(FocusTabAction(tab: widget.bloc.tabs[index]));
-    }
-    _isDragging = true;
-    _dragStartIndex = index;
-    _ghostIndex = index;
-    double scrollOffset = 0.0;
-    if (_scrollController.hasClients) {
-      scrollOffset = _scrollController.offset;
-    }
-
-    // The distance between the mouse cursor and the moving tab's left edge
-    // on the X-axis.
-    _dragCursorOffset = (_tabWidth * _dragStartIndex - scrollOffset) -
-        details.globalPosition.dx;
-  }
-
-  // Updates the moving tab's position as the mouse cursor moves.
-  void _onDragUpdate(DragUpdateDetails details) {
-    double scrollOffset = 0.0;
-    double dragXMax = _tabListWidth - _tabWidth;
-
-    if (_scrollController.hasClients) {
-      scrollOffset = _scrollController.offset;
-      dragXMax = (_kMinTabWidth * widget.bloc.tabs.length) - _kMinTabWidth;
-    }
-    double newX = details.globalPosition.dx + _dragCursorOffset + scrollOffset;
-    _currentTabX.value = newX.clamp(0.0, dragXMax);
-
-    if (_scrollController.hasClients) {
-      if (_didHitLeftEdge()) {
-        _autoScrollTo(_ScrollDirection.left);
-      } else if (_didHitRightEdge()) {
-        _autoScrollTo(_ScrollDirection.right);
-      }
-    }
-  }
-
-  void _onDragEnd(DragEndDetails details) {
-    _isDragging = false;
-
-    // Rearranges the selected tab to the currently empty space only when
-    // there is no unfinished animations.
-    if (!_isAnimating) {
-      _completeRearrangement();
-    }
-  }
-
-  void _onLeftNewGhostControllerChanged(AnimationStatus status) {
-    if (status == AnimationStatus.completed) {
-      _isAnimating = false;
-      --_ghostIndex;
-      _ghostController.value = 1.0;
-      _leftNewGhostController.value = 0.0;
-
-      _onAnimationInterrupted();
-    }
-  }
-
-  void _onCurrentTabXChanged() {
-    if (_isDragging) {
-      // Sees if the moving tab is overlapping more than half of its neighbor tab,
-      // then shift the overlapped tab to the left/right accordingly.
-      // Does not check while a shifting animation is happening.
-      if (!_isAnimating) {
-        if (_isOverlappingLeftTabHalf()) {
-          _shiftLeftToRight();
-        }
-        if (_isOverlappingRightTabHalf()) {
-          _shiftRightToLeft();
-        }
-      }
-    }
-  }
-
-  void _onRightNewGhostControllerChanged(AnimationStatus status) {
-    if (status == AnimationStatus.completed) {
-      _isAnimating = false;
-      ++_ghostIndex;
-      _ghostController.value = 1.0;
-      _rightNewGhostController.value = 0.0;
-
-      _onAnimationInterrupted();
-    }
-  }
-
-  // Checks if the DragEnd event occurs before a spacing;s size transition animation
-  // finishes, and if so, rearranges the selected tab to the currently empty space.
-  void _onAnimationInterrupted() {
-    if (_isDragging == false) {
-      _completeRearrangement();
-    }
-  }
-
-  // Changes the order of the tabs in the TabsBloc.
-  void _completeRearrangement() {
-    if (_ghostIndex != _dragStartIndex) {
-      widget.bloc.request.add(RearrangeTabsAction(
-        originalIndex: _dragStartIndex,
-        newIndex: _ghostIndex,
-      ));
-    }
-    _currentTabX.value = _tabWidth * _ghostIndex;
-  }
-
-  void _onScrollButtonTap(_ScrollButton button) {
-    if (!button.isEnabled.value) {
-      return;
-    }
-
-    final currentOffset = _scrollController.offset;
-    final newOffset = (_tabListWidth / 2) * button.directionFactor;
-
-    _scrollController.animateTo(
-      currentOffset + newOffset,
-      duration: Duration(milliseconds: _kScrollAnimationDuration),
-      curve: Curves.ease,
-    );
-  }
-
-  void _onScrollEnd(ScrollMetrics metrics) {
-    if (_canScrollTo(_ScrollDirection.left)) {
-      _leftScrollButton.enable();
-    } else {
-      _leftScrollButton.disable();
-    }
-
-    if (_canScrollTo(_ScrollDirection.right)) {
-      _rightScrollButton.enable();
-    } else {
-      _rightScrollButton.disable();
-    }
-  }
-
-  // CHECKERS
-
-  bool _didHitLeftEdge() {
-    final scrollOffset = _scrollController.offset;
-
-    return (_currentTabX.value < scrollOffset);
-  }
-
-  bool _didHitRightEdge() {
-    final scrollOffset = _scrollController.offset;
-    final listViewportWidth = _scrollController.position.viewportDimension;
-
-    return (_currentTabX.value + _kMinTabWidth >
-        scrollOffset + listViewportWidth);
-  }
-
-  bool _isOverlappingLeftTabHalf() {
-    if (_ghostIndex < 1) {
-      return false;
-    }
-
-    double leftTabCenterX = _tabWidth * (_ghostIndex - 1) + (_tabWidth / 2);
-    if (_currentTabX.value < leftTabCenterX) {
-      return true;
-    }
-    return false;
-  }
-
-  bool _isOverlappingRightTabHalf() {
-    if (_ghostIndex > widget.bloc.tabs.length - 2) {
-      return false;
-    }
-    double rightTabCenterX = _tabWidth * (_ghostIndex + 1) + (_tabWidth / 2);
-    if (_currentTabX.value + _tabWidth > rightTabCenterX) {
-      return true;
-    }
-    return false;
-  }
-
-  bool _canScrollTo(_ScrollDirection direction) {
-    switch (direction) {
-      case _ScrollDirection.left:
-        if (_scrollController.offset <= 0.0) {
-          return false;
-        }
-        return true;
-      case _ScrollDirection.right:
-        if (_scrollController.offset >=
-            (_kMinTabWidth * widget.bloc.tabs.length -
-                _scrollController.position.viewportDimension)) {
-          return false;
-        }
-        return true;
-      default:
-        return true;
-    }
-  }
-
-  // ANIMATORS
-
-  void _autoScrollTo(_ScrollDirection direction) {
-    final currentOffset = _scrollController.offset;
-    final addOnOffset = (direction == _ScrollDirection.left)
-        ? _kAutoScrollOffset * -1
-        : _kAutoScrollOffset;
-
-    final offsetMax = (_tabWidth * widget.bloc.tabs.length) -
-        _scrollController.position.viewportDimension;
-
-    final newOffset = (currentOffset + addOnOffset).clamp(0.0, offsetMax);
-
-    final tabXMax = (_tabWidth * widget.bloc.tabs.length) - _tabWidth;
-    _scrollController.jumpTo(newOffset);
-    _currentTabX.value = (_currentTabX.value + addOnOffset).clamp(0.0, tabXMax);
-  }
-
-  void _shiftLeftToRight() {
-    _isAnimating = true;
-    _ghostController.reverse(from: 1.0);
-    _leftNewGhostController.forward(from: 0.0);
-  }
-
-  void _shiftRightToLeft() {
-    _isAnimating = true;
-    _ghostController.reverse(from: 1.0);
-    _rightNewGhostController.forward(from: 0.0);
-  }
-}
-
-/// An individual tab.
-///
-/// Shows the title of its webpage and displays a'close' button when it is selected or
-/// a mouse cursor hovers over it. Also, handles the closing tab event when the close
-/// button is tapped on.
-class _TabWidget extends StatefulWidget {
-  const _TabWidget(
-      {required this.bloc,
-      required this.selected,
-      required this.onSelect,
-      required this.onClose});
-  final WebPageBloc bloc;
-  final bool selected;
-  final VoidCallback onSelect;
-  final VoidCallback onClose;
-
-  @override
-  _TabWidgetState createState() => _TabWidgetState();
-}
-
-class _TabWidgetState extends State<_TabWidget> {
-  final _hovering = ValueNotifier<bool>(false);
-  @override
-  Widget build(BuildContext context) {
-    final baseTheme = Theme.of(context);
-    return MouseRegion(
-      onEnter: (_) {
-        _hovering.value = true;
-      },
-      onExit: (_) {
-        WidgetsBinding.instance?.addPostFrameCallback((_) {
-          _hovering.value = false;
-        });
-      },
-      child: DefaultTextStyle(
-        style: baseTheme.textTheme.bodyText2!.copyWith(
-          color: widget.selected
-              ? baseTheme.colorScheme.primary
-              : baseTheme.colorScheme.secondary,
-        ),
-        child: Stack(
-          children: <Widget>[
-            Center(
-              child: Padding(
-                padding: _kTabPadding,
-                child: AnimatedBuilder(
-                  animation: widget.bloc.pageTitleNotifier,
-                  builder: (_, __) => Text(
-                    widget.bloc.pageTitle ?? Strings.newtab.toUpperCase(),
-                    maxLines: 1,
-                    overflow: TextOverflow.ellipsis,
-                  ),
-                ),
-              ),
-            ),
-            Positioned(
-              right: 0,
-              child: AnimatedBuilder(
-                animation: _hovering,
-                builder: (_, child) => Offstage(
-                  offstage: !(widget.selected || _hovering.value),
-                  child: child,
-                ),
-                child: Padding(
-                  padding: EdgeInsets.all(4),
-                  child: GestureDetector(
-                    key: ValueKey('tab_close'),
-                    onTap: widget.onClose,
-                    child: Container(
-                      color: Colors.transparent,
-                      alignment: Alignment.center,
-                      child: Icon(
-                        Icons.clear,
-                        color: widget.selected
-                            ? baseTheme.colorScheme.primary
-                            : baseTheme.colorScheme.secondary,
-                        size: _kIconSize,
-                      ),
-                    ),
-                  ),
-                ),
-              ),
-            ),
-          ],
-        ),
-      ),
-    );
-  }
-}
-
-class _ScrollButton {
-  final _ScrollDirection type;
-  late IconData icon;
-  final ValueNotifier<bool> isEnabled = ValueNotifier<bool>(true);
-  double directionFactor = 0.0;
-
-  _ScrollButton(this.type)
-      : assert(
-            type == _ScrollDirection.left || type == _ScrollDirection.right) {
-    switch (type) {
-      case _ScrollDirection.left:
-        icon = Icons.keyboard_arrow_left;
-        directionFactor = -1.0;
-        break;
-      default:
-        icon = Icons.keyboard_arrow_right;
-        directionFactor = 1.0;
-        break;
-    }
-  }
-
-  void disable() => isEnabled.value = false;
-  void enable() => isEnabled.value = true;
-}
diff --git a/bin/simple_browser/lib/test_main.dart b/bin/simple_browser/lib/test_main.dart
deleted file mode 100644
index 934097e..0000000
--- a/bin/simple_browser/lib/test_main.dart
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright 2021 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'package:flutter/services.dart';
-import 'package:flutter_driver/driver_extension.dart';
-
-import 'main.dart' as entrypoint;
-
-void main() {
-  final handler = OptionalMethodChannel('flutter_driver/handler');
-  enableFlutterDriverExtension(
-      enableTextEntryEmulation: false,
-      handler: (String? data) async {
-        if (data != null) {
-          final result = await handler.invokeMethod(data);
-          return result.toString();
-        }
-        return '';
-      });
-  entrypoint.main();
-}
diff --git a/bin/simple_browser/meta/simple_browser.cmx b/bin/simple_browser/meta/simple_browser.cmx
deleted file mode 100644
index 00e6a95..0000000
--- a/bin/simple_browser/meta/simple_browser.cmx
+++ /dev/null
@@ -1,29 +0,0 @@
-{
-    "include": [
-        "//src/chromium/web_engine/meta/shards/web_engine_base.shard.cmx",
-        "//src/chromium/web_engine/meta/shards/web_engine_feature_hardware_video_decoder.shard.cmx",
-        "//src/chromium/web_engine/meta/shards/web_engine_feature_network.shard.cmx",
-        "//src/chromium/web_engine/meta/shards/web_engine_view.shard.cmx",
-        "//src/lib/vulkan/application-container.shard.cmx"
-    ],
-    "program": {
-        "data": "data/simple-browser"
-    },
-    "sandbox": {
-        "services": [
-            "fuchsia.cobalt.LoggerFactory",
-            "fuchsia.intl.PropertyProvider",
-            "fuchsia.logger.LogSink",
-            "fuchsia.media.Audio",
-            "fuchsia.media.ProfileProvider",
-            "fuchsia.media.SessionAudioConsumerFactory",
-            "fuchsia.sys.Environment",
-            "fuchsia.sys.Launcher",
-            "fuchsia.ui.input.ImeService",
-            "fuchsia.ui.input.ImeVisibilityService",
-            "fuchsia.ui.policy.Presenter",
-            "fuchsia.ui.shortcut.Registry",
-            "fuchsia.web.ContextProvider"
-        ]
-    }
-}
\ No newline at end of file
diff --git a/bin/simple_browser/pubspec.yaml b/bin/simple_browser/pubspec.yaml
deleted file mode 100644
index 8db4a58..0000000
--- a/bin/simple_browser/pubspec.yaml
+++ /dev/null
@@ -1,6 +0,0 @@
-# Copyright 2019 The Fuchsia Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-name: simple_browser
-description: A simple browser for Fuchsia
\ No newline at end of file
diff --git a/bin/simple_browser/target_test/BUILD.gn b/bin/simple_browser/target_test/BUILD.gn
deleted file mode 100644
index d9f0551..0000000
--- a/bin/simple_browser/target_test/BUILD.gn
+++ /dev/null
@@ -1,26 +0,0 @@
-# Copyright 2020 The Fuchsia Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import("//build/components.gni")
-import("//build/flutter/flutter_test_component.gni")
-
-flutter_test_component("target-test-component") {
-  component_name = "simple-browser-target-test"
-  manifest = "meta/simple_browser_target_test.cmx"
-  sources = [ "simple_browser_target_test.dart" ]
-
-  deps = [
-    "//src/experiences/bin/simple_browser:lib",
-    "//third_party/dart-pkg/git/flutter/packages/flutter_test",
-    "//third_party/dart-pkg/pub/test",
-  ]
-}
-
-# fx test simple-browser-target-test
-fuchsia_test_package("simple-browser-target-test") {
-  test_components = [ ":target-test-component" ]
-  test_specs = {
-    environments = basic_envs
-  }
-}
diff --git a/bin/simple_browser/target_test/meta/simple_browser_target_test.cmx b/bin/simple_browser/target_test/meta/simple_browser_target_test.cmx
deleted file mode 100644
index 55515bd..0000000
--- a/bin/simple_browser/target_test/meta/simple_browser_target_test.cmx
+++ /dev/null
@@ -1,34 +0,0 @@
-{
-    "include": [
-        "//src/lib/vulkan/application-container.shard.cmx"
-    ],
-    "program": {
-        "data": "data/simple-browser-target-test"
-    },
-    "sandbox": {
-        "services": [
-            "fuchsia.accessibility.semantics.SemanticsManager",
-            "fuchsia.cobalt.LoggerFactory",
-            "fuchsia.device.NameProvider",
-            "fuchsia.fonts.Provider",
-            "fuchsia.intl.PropertyProvider",
-            "fuchsia.logger.LogSink",
-            "fuchsia.media.Audio",
-            "fuchsia.media.SessionAudioConsumerFactory",
-            "fuchsia.mediacodec.CodecFactory",
-            "fuchsia.memorypressure.Provider",
-            "fuchsia.net.interfaces.State",
-            "fuchsia.netstack.Netstack",
-            "fuchsia.posix.socket.Provider",
-            "fuchsia.process.Launcher",
-            "fuchsia.sys.Environment",
-            "fuchsia.sys.Launcher",
-            "fuchsia.ui.input.ImeService",
-            "fuchsia.ui.input.ImeVisibilityService",
-            "fuchsia.ui.policy.Presenter",
-            "fuchsia.ui.scenic.Scenic",
-            "fuchsia.ui.shortcut.Registry",
-            "fuchsia.web.ContextProvider"
-        ]
-    }
-}
diff --git a/bin/simple_browser/target_test/pubspec.yaml b/bin/simple_browser/target_test/pubspec.yaml
deleted file mode 100644
index 23ed432..0000000
--- a/bin/simple_browser/target_test/pubspec.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-# Copyright 2019 The Fuchsia Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-name: simple_browser
-description: A simple browser for Fuchsia
-
-flutter:
-  fonts:
-    - family: MaterialIcons
-      fonts:
-        - asset: fonts/MaterialIcons-Regular.otf 
diff --git a/bin/simple_browser/target_test/test/simple_browser_target_test.dart b/bin/simple_browser/target_test/test/simple_browser_target_test.dart
deleted file mode 100644
index f43c614..0000000
--- a/bin/simple_browser/target_test/test/simple_browser_target_test.dart
+++ /dev/null
@@ -1,14 +0,0 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// TODO(https://fxbug.dev/84961): Fix null safety and remove this language version.
-// @dart=2.9
-
-import 'package:test/test.dart';
-
-void main() {
-  test('test stub', () {
-    expect(true, true);
-  });
-}
diff --git a/bin/simple_browser/test/browser_shortcuts_test.dart b/bin/simple_browser/test/browser_shortcuts_test.dart
deleted file mode 100644
index eefffa1..0000000
--- a/bin/simple_browser/test/browser_shortcuts_test.dart
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'package:fidl_fuchsia_ui_shortcut/fidl_async.dart' as ui_shortcut
-    show RegistryProxy;
-import 'package:flutter/material.dart';
-import 'package:fuchsia_logger/logger.dart';
-import 'package:mockito/mockito.dart';
-// ignore_for_file: implementation_imports
-import 'package:simple_browser/src/blocs/tabs_bloc.dart';
-import 'package:simple_browser/src/utils/browser_shortcuts.dart';
-import 'package:test/test.dart';
-
-void main() {
-  late MockRegistryProxy mockRegistryProxy;
-  late MockTabsBloc mockTabsBloc;
-  const List<String> defaultKeys = [
-    'newTab',
-    'closeTab',
-    'goBack',
-    'goForward',
-    'refresh',
-    'previousTab',
-    'nextTab'
-  ];
-
-  setupLogger(name: 'browser_shortcuts_test');
-
-  setUp(() {
-    mockRegistryProxy = MockRegistryProxy();
-    mockTabsBloc = MockTabsBloc();
-  });
-
-  test('Should apply the given action map to BrowserShortcuts', () {
-    Map<String, VoidCallback> testActions = {
-      'action1': () {},
-      'action2': () {},
-      'action3': () {},
-    };
-    BrowserShortcuts bs = BrowserShortcuts(
-      tabsBloc: mockTabsBloc, //tabsBloc,
-      shortcutRegistry: mockRegistryProxy,
-      actions: testActions,
-    );
-
-    expect(bs.actions.length, 3, reason: '''Expected 3 shortcuts,
-            but actually ${bs.actions.length} shortcuts.''');
-    testActions.forEach((key, value) {
-      expect(bs.actions.containsKey(key), true,
-          reason: 'Expected to have $key, but does not.');
-    });
-    for (String key in defaultKeys) {
-      expect(bs.actions.containsKey(key), false,
-          reason: 'Expected not to have $key, but does.');
-    }
-  });
-}
-
-class MockRegistryProxy extends Mock implements ui_shortcut.RegistryProxy {}
-
-class MockTabsBloc extends Mock implements TabsBloc {}
diff --git a/bin/simple_browser/test/sanitize_url_test.dart b/bin/simple_browser/test/sanitize_url_test.dart
deleted file mode 100644
index 90210c6..0000000
--- a/bin/simple_browser/test/sanitize_url_test.dart
+++ /dev/null
@@ -1,79 +0,0 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'package:flutter_test/flutter_test.dart';
-import 'package:fuchsia_logger/logger.dart';
-
-// ignore_for_file: implementation_imports
-import 'package:simple_browser/src/utils/sanitize_url.dart';
-
-void main() {
-  setupLogger(name: 'sanitize_url_test');
-
-  void urlSanitizationTest(String testUrl, String expectedResult) {
-    String testResult = sanitizeUrl(testUrl);
-
-    expect(testResult, expectedResult,
-        reason: 'expected "$expectedResult" but actually "$testResult".');
-  }
-
-  test('URL Sanitization: URLs with supported schemes.', () {
-    // With a URL that causes Format Exception to Dart's Uri.parse().
-    urlSanitizationTest('http ://wrongformat',
-        'https://www.google.com/search?q=http+%3A%2F%2Fwrongformat');
-
-    // With a supported scheme(https) and a valid TLD(com).
-    urlSanitizationTest('https://google.com', 'https://google.com');
-
-    // With a supported scheme(http) and a valid TLD(org).
-    urlSanitizationTest('http://wikipedia.org', 'http://wikipedia.org');
-
-    // With a supported scheme(chrome) and a valid URL.
-    urlSanitizationTest('chrome://gpu', 'chrome://gpu');
-
-    // With a supported scheme(https) and an invalid TLD.
-    urlSanitizationTest('https://google.cooom', 'https://google.cooom');
-
-    // localhost with a port number
-    urlSanitizationTest('localhost:8000', 'localhost:8000');
-  });
-
-  test('URL Sanitization: URLs without schemes.', () {
-    const String googleSearchUrl = 'https://www.google.com/search?q=';
-
-    // With a valid host pattern and a valid TLD.
-    urlSanitizationTest('flutter.dev', 'https://flutter.dev');
-
-    // With a valid host pattern and a valid TLD and a path
-    urlSanitizationTest('flutter.dev/clock', 'https://flutter.dev/clock');
-
-    // With an invalid host pattern and a valid TLD.
-    urlSanitizationTest(
-        'flu#%*tter.dev', '${googleSearchUrl}flu%23%25%2Atter.dev');
-
-    // With a valid host pattern and an invalid TLD.
-    urlSanitizationTest('google.cooom', '${googleSearchUrl}google.cooom');
-
-    // With a keyword.
-    urlSanitizationTest('fuchsia', '${googleSearchUrl}fuchsia');
-
-    // With a valid ip address.
-    urlSanitizationTest('255.111.18.1', 'https://255.111.18.1');
-
-    // With a valid ip address and a path
-    urlSanitizationTest('200.102.11.9/hello', 'https://200.102.11.9/hello');
-
-    // With an invalid ip address (Each number must be a value between 0 - 255).
-    urlSanitizationTest('955.111.18.1', '${googleSearchUrl}955.111.18.1');
-
-    // With an invalid ip address (The address must consist of 4 numbers).
-    urlSanitizationTest('255.111.18', '${googleSearchUrl}255.111.18');
-
-    // With an invalid ip address (Spaced not allowed).
-    urlSanitizationTest('255.111.18. 1', '${googleSearchUrl}255.111.18.+1');
-
-    // With an invalid ip address (Non-digit characters except '.' not allowed).
-    urlSanitizationTest('255.111.18.*1', '${googleSearchUrl}255.111.18.%2A1');
-  });
-}
diff --git a/bin/simple_browser/test/simple_browser_test.dart b/bin/simple_browser/test/simple_browser_test.dart
deleted file mode 100644
index 2e02177..0000000
--- a/bin/simple_browser/test/simple_browser_test.dart
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'package:flutter/material.dart';
-import 'package:flutter_test/flutter_test.dart';
-import 'package:fuchsia_logger/logger.dart';
-import 'package:mockito/mockito.dart';
-
-// ignore_for_file: implementation_imports
-import 'package:simple_browser/app.dart';
-import 'package:simple_browser/src/blocs/tabs_bloc.dart';
-import 'package:simple_browser/src/blocs/webpage_bloc.dart';
-import 'package:simple_browser/src/models/app_model.dart';
-
-void main() {
-  setupLogger(name: 'simple_browser_test');
-
-  Stream<Locale> lstream;
-  TabsBloc tabsBloc;
-
-  testWidgets('localized text is displayed in the widgets',
-      (WidgetTester tester) async {
-    lstream = Stream.fromIterable(
-            [Locale.fromSubtags(languageCode: 'sr', countryCode: 'RS')])
-        .asBroadcastStream();
-
-    tabsBloc = TabsBloc(
-      // TODO(https://fxbug.dev/71711): Figure out why `dart analyze` complains
-      // about this.
-      tabFactory: () => MockWebPageBloc(), // ignore: unnecessary_lambdas
-      disposeTab: (tab) {
-        tab.dispose();
-      },
-    );
-
-    final model = MockAppModel();
-
-    when(model.tabsBloc).thenAnswer((_) => tabsBloc);
-    when(model.localeStream).thenAnswer((_) => lstream);
-
-    final app = App(model);
-
-    await tester.pumpWidget(app);
-    await tester.pumpAndSettle();
-
-    expect(
-        find.byWidgetPredicate(
-            (Widget widget) => widget is Title && widget.title == 'Прегледач',
-            description: 'A widget with a localized title was displayed'),
-        findsOneWidget);
-  });
-}
-
-class MockAppModel extends Mock implements AppModel {}
-
-class MockWebPageBloc extends Mock implements WebPageBloc {}
diff --git a/bin/simple_browser/test/tabs_bloc_test.dart b/bin/simple_browser/test/tabs_bloc_test.dart
deleted file mode 100644
index 713e9d8..0000000
--- a/bin/simple_browser/test/tabs_bloc_test.dart
+++ /dev/null
@@ -1,532 +0,0 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'dart:async';
-import 'package:flutter/foundation.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter_test/flutter_test.dart';
-import 'package:fuchsia_logger/logger.dart';
-import 'package:mockito/mockito.dart';
-
-// ignore_for_file: implementation_imports
-import 'package:simple_browser/src/blocs/tabs_bloc.dart';
-import 'package:simple_browser/src/blocs/webpage_bloc.dart';
-import 'package:simple_browser/src/models/tabs_action.dart';
-
-void main() {
-  setupLogger(name: 'tabs_bloc_test');
-
-  group('newTab', () {
-    test('Add 1 tab.', () async {
-      // Creates one tab.
-      final tb = await _creatNTabs(1);
-
-      // Sees if there is only one tab.
-      int actual = tb.tabs.length;
-      int expected = 1;
-      expect(
-        actual,
-        expected,
-        reason: _expectNTabsReason(
-          actual.toString(),
-          expected.toString(),
-        ),
-      );
-    });
-
-    test('Add 2 tabs.', () async {
-      // Creates two tabs.
-      final tb = await _creatNTabs(2);
-
-      // Sees if there are two tabs.
-      int actual = tb.tabs.length;
-      int expected = 2;
-      expect(
-        actual,
-        expected,
-        reason: _expectNTabsReason(
-          actual.toString(),
-          expected.toString(),
-        ),
-      );
-    });
-  });
-
-  group('focusTab', () {
-    test('Add 3 tabs, focus on the first tab and then the second tab.',
-        () async {
-      // Test Environment Set Up:
-      // Creates three tabs.
-      final tb = await _creatNTabs(3);
-
-      // Makes sure that the last tab is currently focused.
-      int actual = tb.currentTabIdx!;
-      int expected = 2;
-      expect(
-        actual,
-        expected,
-        reason: _expectFocusedTabReason(
-          actual.toString(),
-          expected.toString(),
-        ),
-      );
-
-      // Changes the focus to the first tab.
-      await _focusTab(tb, tb.tabs[0]);
-
-      // Sees if the index of the newly focused tab is 0.
-      actual = tb.currentTabIdx!;
-      expected = 0;
-      expect(
-        actual,
-        expected,
-        reason: _expectFocusedTabReason(
-          actual.toString(),
-          expected.toString(),
-        ),
-      );
-
-      // Changes the focus to the second tab.
-      await _focusTab(tb, tb.tabs[1]);
-
-      // Sees if the index of the newly focused tab is 1.
-      actual = tb.currentTabIdx!;
-      expected = 1;
-      expect(
-        actual,
-        expected,
-        reason: _expectFocusedTabReason(
-          actual.toString(),
-          expected.toString(),
-        ),
-      );
-    });
-  });
-
-  group('removeTab', () {
-    test('Add 1 tab and remove it.', () async {
-      // Test Environment Set Up:
-      // Creates one tab.
-      final tb = await _creatNTabs(1);
-
-      // Makes sure that there is only one tab.
-      int actual = tb.tabs.length;
-      int expected = 1;
-      expect(
-        actual,
-        expected,
-        reason: _expectNTabsReason(
-          actual.toString(),
-          expected.toString(),
-        ),
-      );
-
-      // Closes the tab.
-      await _closeTab(tb, tb.currentTab!);
-
-      // Sees if there is no tabs anyumore.
-      actual = tb.tabs.length;
-      expected = 0;
-      expect(
-        actual,
-        expected,
-        reason: _expectNTabsReason(
-          actual.toString(),
-          expected.toString(),
-        ),
-      );
-
-      // Sees if the currentTab indicates null.
-      expect(
-        tb.currentTab,
-        null,
-        reason: _expectFocusedTabReason(
-          (tb.currentTabIdx).toString(),
-          'null',
-        ),
-      );
-    });
-
-    test(
-        'Add 3 tabs, and remove the currently focused tab, which is the last tab.',
-        () async {
-      // Test Environment Set Up:
-      // Creates three tabs and focus on the first tab.
-      final tb = await _creatNTabs(3);
-
-      // Makes sure that the last tab is currently focused.
-      int actualFocusedTabIdx = tb.currentTabIdx!;
-      int expectedFocusedTabIdx = 2;
-      expect(
-        actualFocusedTabIdx,
-        expectedFocusedTabIdx,
-        reason: _expectFocusedTabReason(
-          actualFocusedTabIdx.toString(),
-          expectedFocusedTabIdx.toString(),
-        ),
-      );
-
-      // Saves the tab previous to the currently focused tab. (the second tab)
-      final expectedTab = tb.tabs[1];
-
-      // Closes the currently focused tab. (the last tab)
-      await _closeTab(tb, tb.currentTab!);
-
-      // Sees if the number of the remaining tabs is 2.
-      int actualNumTabs = tb.tabs.length;
-      int expectedNumTabs = 2;
-      expect(
-        actualNumTabs,
-        expectedNumTabs,
-        reason: _expectNTabsReason(
-          actualNumTabs.toString(),
-          expectedNumTabs.toString(),
-        ),
-      );
-
-      // Sees if the newly focused tab is the tab previous to the removed tab.
-      expect(tb.currentTab, expectedTab,
-          reason:
-              '''The currently focused tab is expected to be the previous one to the removed one,
-              but is actually not.''');
-
-      // Sees if the index of the newly focused tab is still 1.
-      actualFocusedTabIdx = tb.currentTabIdx!;
-      expectedFocusedTabIdx = 1;
-      expect(
-        actualFocusedTabIdx,
-        expectedFocusedTabIdx,
-        reason: _expectFocusedTabReason(
-          actualFocusedTabIdx.toString(),
-          expectedFocusedTabIdx.toString(),
-        ),
-      );
-    });
-
-    test('Add 3 tabs, focus on the first tab, and remove it.', () async {
-      // Test Environment Set Up:
-      // Creates three tabs and focus on the first tab.
-      final tb = await _creatNTabs(3);
-      await _focusTab(tb, tb.tabs[0]);
-
-      // Makes sure that the first tab is currently focused.
-      int actualFocusedTabIdx = tb.currentTabIdx!;
-      int expectedFocusedTabIdx = 0;
-      expect(
-        actualFocusedTabIdx,
-        expectedFocusedTabIdx,
-        reason: _expectFocusedTabReason(
-          actualFocusedTabIdx.toString(),
-          expectedFocusedTabIdx.toString(),
-        ),
-      );
-
-      // Saves the tab next to the currently focused tab. (the second tab)
-      final expectedTab = tb.tabs[1];
-
-      // Closes the currently focused tab. (the first tab)
-      await _closeTab(tb, tb.currentTab!);
-
-      // Sees if the number of the remaining tabs is 2.
-      int actualNumTabs = tb.tabs.length;
-      int expectedNumTabs = 2;
-      expect(
-        actualNumTabs,
-        expectedNumTabs,
-        reason: _expectNTabsReason(
-          actualNumTabs.toString(),
-          expectedNumTabs.toString(),
-        ),
-      );
-
-      // Sees if the newly focused tab is the tab next to the closed tab.
-      expect(tb.currentTab, expectedTab,
-          reason:
-              '''The currently focused tab is expected to be the next one to the removed one,
-              but is actually not.''');
-
-      // Sees if the index of the newly focused tab has been shifted to 0.
-      actualFocusedTabIdx = tb.currentTabIdx!;
-      expectedFocusedTabIdx = 0;
-      expect(
-        actualFocusedTabIdx,
-        expectedFocusedTabIdx,
-        reason: _expectFocusedTabReason(
-          actualFocusedTabIdx.toString(),
-          expectedFocusedTabIdx.toString(),
-        ),
-      );
-    });
-
-    test('Add 3 tabs and remove the first one.', () async {
-      // Test Environment Set Up:
-      // Creates three tabs.
-      final tb = await _creatNTabs(3);
-
-      // Makes sure that the last tab is currently focused.
-      int actualFocusedTabIdx = tb.currentTabIdx!;
-      int expectedFocusedTabIdx = 2;
-      expect(
-        actualFocusedTabIdx,
-        expectedFocusedTabIdx,
-        reason: _expectFocusedTabReason(
-          actualFocusedTabIdx.toString(),
-          expectedFocusedTabIdx.toString(),
-        ),
-      );
-
-      // Saves the currently focused tab. (the last tab)
-      final expectedTab = tb.currentTab;
-
-      // Closes the first tab.
-      await _closeTab(tb, tb.tabs[0]);
-
-      // Sees if the number of the remaining tabs is 2.
-      int actualNumTabs = tb.tabs.length;
-      int expectedNumTabs = 2;
-      expect(
-        actualNumTabs,
-        expectedNumTabs,
-        reason: _expectNTabsReason(
-          actualNumTabs.toString(),
-          expectedNumTabs.toString(),
-        ),
-      );
-
-      // Sees if the currently focused tab is still the same one.
-      expect(tb.currentTab, expectedTab,
-          reason:
-              '''The focused tab is expected to be the same before and after closing the second tab,
-              but is actually different.''');
-
-      // Sees if the index of the currently focused tab has been shifted to 1.
-      actualFocusedTabIdx = tb.currentTabIdx!;
-      expectedFocusedTabIdx = 1;
-      expect(
-        actualFocusedTabIdx,
-        expectedFocusedTabIdx,
-        reason: _expectFocusedTabReason(
-          actualFocusedTabIdx.toString(),
-          expectedFocusedTabIdx.toString(),
-        ),
-      );
-    });
-
-    test('Add 5 tabs and move a tab to the left and right.', () async {
-      // Test Environment Set Up:
-      // Creates five tabs.
-      final tb = await _creatNTabs(5);
-
-      // Makes sure that the last tab is currently focused.
-      int actualFocusedTabIdx = tb.currentTabIdx!;
-      int expectedFocusedTabIdx = 4;
-      expect(
-        actualFocusedTabIdx,
-        expectedFocusedTabIdx,
-        reason: _expectFocusedTabReason(
-          actualFocusedTabIdx.toString(),
-          expectedFocusedTabIdx.toString(),
-        ),
-      );
-
-      // Saves the current tabs.
-      WebPageBloc tab0BeforeMove = tb.tabs[0];
-      WebPageBloc tab1BeforeMove = tb.tabs[1];
-      WebPageBloc tab2BeforeMove = tb.tabs[2];
-      WebPageBloc tab3BeforeMove = tb.tabs[3];
-      WebPageBloc tab4BeforeMove = tb.tabs[4];
-
-      // Moves the currently focused tab (index 4) to the 3rd tab(index 2) position.
-      int indexToMove = 4;
-      int indexToBe = 2;
-      await _rearrangeTabs(tb, indexToMove, indexToBe);
-
-      // Sees if the index of the currently focused tab is now 2.
-      actualFocusedTabIdx = tb.currentTabIdx!;
-      expectedFocusedTabIdx = indexToBe;
-      expect(
-        actualFocusedTabIdx,
-        expectedFocusedTabIdx,
-        reason:
-            '''Expected the currently focused tab index to be $expectedFocusedTabIdx,
-        but actually, is $actualFocusedTabIdx.''',
-      );
-
-      // Sees if all tabs have been rarranged correctly.
-      expect(tb.tabs[0], tab0BeforeMove,
-          reason: 'Expected that the 1st tab used to be the 1st.');
-      expect(tb.tabs[1], tab1BeforeMove,
-          reason: 'Expected that the 2nd tab used to be the 2nd.');
-      expect(tb.tabs[2], tab4BeforeMove,
-          reason: 'Expected that the 3rd tab used to be the 5th.');
-      expect(tb.tabs[3], tab2BeforeMove,
-          reason: 'Expected that the 4th tab used to be the 3rd.');
-
-      expect(tb.tabs[4], tab3BeforeMove,
-          reason: 'Expected that the 5th tab used to the 4th.');
-
-      // Saves the rearranged tabs.
-      tab0BeforeMove = tb.tabs[0];
-      tab1BeforeMove = tb.tabs[1];
-      tab2BeforeMove = tb.tabs[2];
-      tab3BeforeMove = tb.tabs[3];
-      tab4BeforeMove = tb.tabs[4];
-
-      // Moves the 2nd tab(index 1) to the 4th tab(index 3) position.
-      indexToMove = 1;
-      indexToBe = 3;
-      await _rearrangeTabs(tb, indexToMove, indexToBe);
-
-      // Sees if the currently focused tab has been shifted to the right and now has
-      // 3 as its index.
-      actualFocusedTabIdx = tb.currentTabIdx!;
-      expectedFocusedTabIdx = 1;
-      expect(
-        actualFocusedTabIdx,
-        expectedFocusedTabIdx,
-        reason: '''Expected that the currently focused tab has been moved to
-        index $expectedFocusedTabIdx, but actually, its index is $actualFocusedTabIdx.''',
-      );
-
-      // Sees if all tabs have been rarranged correctly.
-      expect(tb.tabs[0], tab0BeforeMove,
-          reason: 'Expected that the 1st tab used to be the 1st.');
-      expect(tb.tabs[1], tab2BeforeMove,
-          reason: 'Expected that the 2nd tab used to be the 3rd.');
-      expect(tb.tabs[2], tab3BeforeMove,
-          reason: 'Expected that the 3rd tab used to be the 4th.');
-      expect(tb.tabs[3], tab1BeforeMove,
-          reason: 'Expected that the 4th tab used to be the 2nd.');
-
-      expect(tb.tabs[4], tab4BeforeMove,
-          reason: 'Expected that the 5th tab used to the 5th.');
-    });
-  });
-
-  group('isOnlyTab, previousTab, and nextTab getters', () {
-    test(
-        '''isOnlyTab getter should return true when there is only one tab in the TabsBloc,
-      and return false when another tab is added.''', () async {
-      // Creates one tab.
-      final tb = await _creatNTabs(1);
-
-      // Sees if isOnlyTab getter returns true.
-      expect(
-        tb.isOnlyTab,
-        true,
-        reason:
-            'isOnlyTab is expected to be true, but is actually ${tb.isOnlyTab.toString()}.',
-      );
-
-      // Creates one more tab.
-      await _newTab(tb);
-
-      // Sees if isOnlyTab getter returns false.
-      expect(
-        tb.isOnlyTab,
-        false,
-        reason:
-            'isOnlyTab is expected to be false, but is actually ${tb.isOnlyTab.toString()}.',
-      );
-    });
-
-    test(
-        '''previousTab getter should return the previous tab to the currently focused tab,
-    and the nextTab getter should return the next tab to the currently focused tab.''',
-        () async {
-      // Creates two tabs.
-      final tb = await _creatNTabs(2);
-
-      // Sees if the previousTab getter returns the first tab.
-      expect(
-        tb.previousTab,
-        tb.tabs.first,
-        reason: '''previousTab is expected to be the first tab,
-        but is actually ${tb.tabs.indexOf(tb.previousTab)}th tab.''',
-      );
-
-      // Changes the focus to the first tab.
-      await _focusTab(tb, tb.tabs[0]);
-
-      // Sees if the nextTab getter returns the second(last) tab.
-      expect(
-        tb.nextTab,
-        tb.tabs.last,
-        reason: '''nextTab is expected to be the last tab,
-        but is actually ${tb.tabs.indexOf(tb.nextTab)}th tab.''',
-      );
-    });
-  });
-}
-
-Future<TabsBloc> _creatNTabs(int n) async {
-  TabsBloc tb = TabsBloc(
-    // TODO(https://fxbug.dev/71711): Figure out why `dart analyze` complains
-    // about this.
-    tabFactory: () => MockWebPageBloc(), // ignore: unnecessary_lambdas
-    disposeTab: (tab) => tab.dispose(),
-  );
-
-  for (int i = 0; i < n; i++) {
-    await _newTab(tb);
-  }
-  return tb;
-}
-
-String _expectNTabsReason(String actual, String expected) =>
-    'TabsBloc is expected to have $expected tabs, but actually has $actual tabs.';
-
-String _expectFocusedTabReason(String actual, String expected) =>
-    'The index of the currently focused tab is expected to be $expected, but is actually $actual.';
-
-/// awaits for a single callback from a [Listenable]
-Future _awaitListenable(Listenable listenable) {
-  final c = Completer();
-  late VoidCallback l;
-  l = () {
-    c.complete();
-    listenable.removeListener(l);
-  };
-  listenable.addListener(l);
-  return c.future;
-}
-
-/// sends an [TabsAction] to [TabsBloc] and awaits for a callback in a [Listenable]
-Future _addActionAndAwait(
-  TabsBloc tb,
-  Listenable listenable,
-  TabsAction action,
-) async {
-  tb.request.add(action);
-  await _awaitListenable(listenable);
-}
-
-/// adds a new tab to a [TabsBloc] and awaits completion with [TabsBloc.tabs]
-Future _newTab(TabsBloc tb) => _addActionAndAwait(
-      tb,
-      tb.tabsNotifier,
-      NewTabAction(),
-    );
-
-/// sets focus to a tab in [TabsBloc] and awaits completion with [TabsBloc.currentTab]
-Future _focusTab(TabsBloc tb, WebPageBloc tab) => _addActionAndAwait(
-      tb,
-      tb.currentTabNotifier,
-      FocusTabAction(tab: tab),
-    );
-
-Future _closeTab(TabsBloc tb, WebPageBloc tab) => _addActionAndAwait(
-      tb,
-      tb.tabsNotifier,
-      RemoveTabAction(tab: tab),
-    );
-
-Future _rearrangeTabs(TabsBloc tb, int startIndex, int endIndex) =>
-    _addActionAndAwait(
-      tb,
-      tb.tabsNotifier,
-      RearrangeTabsAction(originalIndex: startIndex, newIndex: endIndex),
-    );
-
-class MockWebPageBloc extends Mock implements WebPageBloc {}
diff --git a/bin/simple_browser/test/tld_checker_test.dart b/bin/simple_browser/test/tld_checker_test.dart
deleted file mode 100644
index f5c36d4..0000000
--- a/bin/simple_browser/test/tld_checker_test.dart
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'package:fuchsia_logger/logger.dart';
-// ignore_for_file: implementation_imports
-import 'package:simple_browser/src/utils/tld_checker.dart';
-import 'package:simple_browser/src/utils/tlds_provider.dart';
-import 'package:test/test.dart';
-
-void main() {
-  setupLogger(name: 'tld_checker_test');
-  test('TLD Validity check test with local TLD list.', () {
-    expect(TldChecker().isValid('dev'), true, reason: '"dev" should be valid.');
-    expect(TldChecker().isValid('asdf'), false,
-        reason: '"asdf" should be invalid.');
-  });
-
-  test('Fetch test with two valid TLDs and one comment line.', () async {
-    String testData = '''
-    # version 2019111500, Last Updated Fri Nov 15 07:07:01 2019 UTC
-    AAA
-    BBB
-    ''';
-    TldsProvider provider = TldsProvider()..data = testData;
-    List<String>? testTlds = await provider.fetchTldsList();
-    TldChecker().prefetchTlds(testTlds: testTlds);
-
-    expect(TldChecker().validTlds.length, 2,
-        reason: 'The length of valid TLD list should be two.');
-    expect(TldChecker().isValid('aaa'), true, reason: '"aaa" should be valid.');
-    expect(TldChecker().isValid('bbb'), true, reason: '"bbb should be valid.');
-  });
-
-  test('Fetch test with one valid TLDs and multiple comment lines.', () async {
-    String testData = '''
-    # version 2019111500, Last Updated Fri Nov 15 07:07:01 2019 UTC
-    # Lorem ipsum dolor sit amet, consectetur adipiscing elit.
-    # Donec sed libero at lacus blandit scelerisque.
-    # Aliquam at felis ac orci facilisis tincidunt.
-    # Nullam eu justo faucibus, pretium urna et, pretium magna.
-    # Duis faucibus elit id felis sollicitudin, quis fermentum neque convallis.
-    QQQ
-    ''';
-
-    TldsProvider provider = TldsProvider()..data = testData;
-    List<String>? testTlds = await provider.fetchTldsList();
-    TldChecker().prefetchTlds(testTlds: testTlds);
-
-    expect(TldChecker().validTlds.length, 1,
-        reason: 'The length of valid TLD list should be one.');
-    expect(TldChecker().isValid('qqq'), true, reason: '"qqq" should be valid.');
-  });
-}
diff --git a/bin/simple_browser/test/webpage_bloc_test.dart b/bin/simple_browser/test/webpage_bloc_test.dart
deleted file mode 100644
index 163f72b..0000000
--- a/bin/simple_browser/test/webpage_bloc_test.dart
+++ /dev/null
@@ -1,237 +0,0 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'package:fidl_fuchsia_web/fidl_async.dart' as web;
-import 'package:fuchsia_logger/logger.dart';
-import 'package:mockito/mockito.dart';
-// ignore_for_file: implementation_imports
-import 'package:simple_browser/src/blocs/webpage_bloc.dart';
-import 'package:simple_browser/src/models/webpage_action.dart';
-import 'package:simple_browser/src/services/simple_browser_navigation_event_listener.dart';
-import 'package:simple_browser/src/services/simple_browser_web_service.dart';
-import 'package:test/test.dart';
-
-void main() {
-  setupLogger(name: 'webpage_bloc_test');
-
-  late MockSimpleBrowserWebService mockSimpleBrowserWebService;
-  late MockNavigationState mockNavigationState;
-  late WebPageBloc webPageBloc;
-  late SimpleBrowserNavigationEventListener
-      simpleBrowserNavigationEventListener;
-
-  setUp(() {
-    mockSimpleBrowserWebService = MockSimpleBrowserWebService();
-    mockNavigationState = MockNavigationState();
-    simpleBrowserNavigationEventListener =
-        SimpleBrowserNavigationEventListener();
-    when(mockSimpleBrowserWebService.navigationEventListener)
-        .thenReturn(simpleBrowserNavigationEventListener);
-    webPageBloc = WebPageBloc(
-      webService: mockSimpleBrowserWebService,
-    );
-  });
-
-  /// Tests if relavent getters in [WebPageBloc] are properly getting updated values
-  /// in [SimpleBrowserNavigationEventListener] when [NavigationState] is changed.
-  group('onNavigationStateChanged', () {
-    test('''WebPageBloc.url should be updated
-        when NavigationState.url changed.''', () {
-      //  url should be an empty string by default.
-      expect(webPageBloc.url, '',
-          reason: '''The initial value of url is expected to be a blank,
-          but is actually ${webPageBloc.url}.''');
-
-      // When NavigationState.url is changed to 'https://www.flutter.dev'.
-      String testUrl = 'https://www.flutter.dev';
-      when(mockNavigationState.url).thenReturn(testUrl);
-      simpleBrowserNavigationEventListener
-          .onNavigationStateChanged(mockNavigationState);
-      expect(webPageBloc.url, testUrl,
-          reason: '''url value is expected to be updated to $testUrl,
-          but is actually ${webPageBloc.url}.''');
-    });
-
-    test('''WebPageBloc.forwardState should be updated
-        when NavigationState.canGoForward changed.''', () {
-      // forwardState should be false by default.
-      expect(webPageBloc.forwardState, false,
-          reason: '''The initial value of forwardState is expected to be false,
-          but is actually ${webPageBloc.forwardState}.''');
-
-      // when NavigationState.canGoForward is changed to true.
-      when(mockNavigationState.canGoForward).thenReturn(true);
-      simpleBrowserNavigationEventListener
-          .onNavigationStateChanged(mockNavigationState);
-      expect(webPageBloc.forwardState, true,
-          reason: '''forwardState is expected to be updated to true,
-          but is actually ${webPageBloc.forwardState}.''');
-    });
-
-    test('''WebPageBloc.backState should be updated
-        when NavigationState.canGoBack changed.''', () {
-      // backState should be false by default.
-      expect(webPageBloc.backState, false,
-          reason: '''The initial value of backState is expected to be false,
-          but is actually ${webPageBloc.backState}.''');
-
-      // when NavigationState.canGoBack is changed to true.
-      when(mockNavigationState.canGoBack).thenReturn(true);
-      simpleBrowserNavigationEventListener
-          .onNavigationStateChanged(mockNavigationState);
-      expect(webPageBloc.backState, true,
-          reason: '''backState is expected to be updated to true,
-          but is actually ${webPageBloc.backState}.''');
-    });
-
-    test('''WebPageBloc.isLoadedState should be updated
-        when NavigationState.isMainDocumentLoaded changed.''', () {
-      // isLoadedState should be true by default.
-      expect(webPageBloc.isLoadedState, true,
-          reason: '''The initial value of isLoadedState is expected to be true,
-          but is actually ${webPageBloc.isLoadedState}.''');
-
-      // when NavigationState.isMainDocumentLoaded is changed to false.
-      when(mockNavigationState.isMainDocumentLoaded).thenReturn(false);
-      simpleBrowserNavigationEventListener
-          .onNavigationStateChanged(mockNavigationState);
-      expect(webPageBloc.isLoadedState, false,
-          reason: '''isLoadedState is expected to be updated to false,
-          but is actually ${webPageBloc.isLoadedState}.''');
-    });
-
-    test('''WebPageBloc.pageTitle should be updated
-        when NavigationState.title changed.''', () {
-      // pageTitle has no default value.
-
-      // when NavigationState.title is changed to 'test'.
-      String testTitle = 'test';
-      when(mockNavigationState.title).thenReturn(testTitle);
-      simpleBrowserNavigationEventListener
-          .onNavigationStateChanged(mockNavigationState);
-      expect(webPageBloc.pageTitle, testTitle,
-          reason: '''pageTitle is expected to be updated to $testTitle,
-          but is actually ${webPageBloc.pageTitle}.''');
-    });
-
-    test('''WebPageBloc.pageType should be updated
-        when NavigationState.pageType changed.''', () {
-      //  pageType should be PageType.empty by default.
-      expect(webPageBloc.pageType, PageType.empty,
-          reason:
-              '''The initial value of pageType is expected to be PageType.empty,
-              but is actually ${webPageBloc.pageType.toString()}.''');
-
-      // when NavigationState.pageType is changed to web.PageType.normal.
-      web.PageType testPageType = web.PageType.normal;
-      when(mockNavigationState.pageType).thenReturn(testPageType);
-      simpleBrowserNavigationEventListener
-          .onNavigationStateChanged(mockNavigationState);
-      expect(webPageBloc.pageType, PageType.normal,
-          reason: '''pageType is expected to be updated to PageType.normal,
-              but is actually ${webPageBloc.pageType.toString()}.''');
-
-      // when NavigationState.pageType is changed to web.PageType.error.
-      testPageType = web.PageType.error;
-      when(mockNavigationState.pageType).thenReturn(testPageType);
-      simpleBrowserNavigationEventListener
-          .onNavigationStateChanged(mockNavigationState);
-      expect(webPageBloc.pageType, PageType.error,
-          reason: '''pageType is expected to be updated to PageType.error,
-              but is actually ${webPageBloc.pageType.toString()}.''');
-    });
-  });
-
-  /// Tests [WebPageBloc]'s [StreamController] and its callback [_onActionChanged].
-  ///
-  /// Verify if relavent methods in [SimpleBrowserWebService] are called,
-  /// and irrelavent ones are not called through the callback when a [WebPageAction]
-  /// is added to the bloc.
-  group('Handling actions', () {
-    test('''
-      Should call NavigationControllerProxy.loadUrl() with the given url
-      when NavigateToAction is added to the webPageBloc with a normal url.
-      ''', () async {
-      String testUrl = 'https://www.google.com';
-
-      webPageBloc.request.add(NavigateToAction(url: testUrl));
-
-      await untilCalled(mockSimpleBrowserWebService.loadUrl(any));
-      verify(webPageBloc.webService.loadUrl(testUrl)).called(1);
-      verifyNever(webPageBloc.webService.goBack());
-      verifyNever(webPageBloc.webService.goForward());
-      verifyNever(webPageBloc.webService.refresh());
-    });
-
-    test('''
-      Should call NavigationControllerProxy.loadUrl() with the given url
-      when NavigateToAction is added to the webPageBloc with a search query url.
-      ''', () async {
-      String testUrl = 'https://www.google.com/search?q=cat';
-
-      webPageBloc.request.add(NavigateToAction(url: testUrl));
-
-      await untilCalled(mockSimpleBrowserWebService.loadUrl(any));
-      verify(webPageBloc.webService.loadUrl(testUrl)).called(1);
-      verifyNever(webPageBloc.webService.goBack());
-      verifyNever(webPageBloc.webService.goForward());
-      verifyNever(webPageBloc.webService.refresh());
-    });
-
-    test('''
-      Should call NavigationControllerProxy.goBack()
-      when GoBackAction is added to the webPageBloc.
-      ''', () async {
-      webPageBloc.request.add(GoBackAction());
-
-      await untilCalled(webPageBloc.webService.goBack());
-      verify(webPageBloc.webService.goBack()).called(1);
-      verifyNever(mockSimpleBrowserWebService.loadUrl(any));
-      verifyNever(webPageBloc.webService.goForward());
-      verifyNever(webPageBloc.webService.refresh());
-    });
-
-    test('''
-      Should call NavigationControllerProxy.goForward()
-      when GoBackAction is added to the webPageBloc.
-      ''', () async {
-      webPageBloc.request.add(GoForwardAction());
-
-      await untilCalled(webPageBloc.webService.goForward());
-      verify(webPageBloc.webService.goForward()).called(1);
-      verifyNever(mockSimpleBrowserWebService.loadUrl(any));
-      verifyNever(webPageBloc.webService.goBack());
-      verifyNever(webPageBloc.webService.refresh());
-    });
-
-    test('''
-      Should call NavigationControllerProxy.reload()
-      when RefreshAction is added to the webPageBloc.
-      ''', () async {
-      webPageBloc.request.add(RefreshAction());
-
-      await untilCalled(webPageBloc.webService.refresh());
-      verify(webPageBloc.webService.refresh()).called(1);
-      verifyNever(mockSimpleBrowserWebService.loadUrl(any));
-      verifyNever(webPageBloc.webService.goBack());
-      verifyNever(webPageBloc.webService.goForward());
-    });
-  });
-}
-
-class MockSimpleBrowserWebService extends Mock
-    implements SimpleBrowserWebService {
-  @override
-  Future<void> loadUrl(String? url) =>
-      super.noSuchMethod(Invocation.method(#loadUrl, [url]));
-}
-
-class MockNavigationState extends Mock implements web.NavigationState {
-  @override
-  bool operator ==(dynamic other) =>
-      super.noSuchMethod(Invocation.setter(#==, other));
-
-  @override
-  int get hashCode => super.noSuchMethod(Invocation.getter(#hashcode));
-}
diff --git a/bin/simple_browser/test/widgets/error_page_test.dart b/bin/simple_browser/test/widgets/error_page_test.dart
deleted file mode 100644
index 8f998bb..0000000
--- a/bin/simple_browser/test/widgets/error_page_test.dart
+++ /dev/null
@@ -1,75 +0,0 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'package:flutter/material.dart';
-import 'package:flutter_test/flutter_test.dart';
-import 'package:fuchsia_logger/logger.dart';
-
-// ignore_for_file: implementation_imports
-import 'package:simple_browser/src/widgets/error_page.dart';
-
-void main() {
-  setupLogger(name: 'error_page_test');
-
-  late double bodyWidth;
-  late double bodyHeight;
-
-  setUpAll(() {
-    bodyWidth = 800.0;
-    bodyHeight = 600.0;
-  });
-
-  testWidgets('There should be 5 text widgets: E,R,R,O,R.',
-      (WidgetTester tester) async {
-    await _setUpErrorPage(tester, bodyWidth, bodyHeight);
-
-    // Sees if there are one ‘E’, one ‘O’ and three ‘R’ texts.
-    expect(find.text('E'), findsOneWidget,
-        reason: 'Expected an E on the error page.');
-    expect(find.text('O'), findsOneWidget,
-        reason: 'Expected an O on the error page.');
-    expect(find.text('R'), findsNWidgets(3),
-        reason: 'Expected three Rs on the error page.');
-  });
-
-  testWidgets('There should be 5 Positioned widgets in the intended order.',
-      (WidgetTester tester) async {
-    await _setUpErrorPage(tester, bodyWidth, bodyHeight);
-
-    // Sees if there are 5 Positioned widgets
-    expect(find.byType(Positioned), findsNWidgets(5),
-        reason: 'Expected 5 Positioned widgets on the error page.');
-
-    // Sees if all those Positioned widgets are positioned on the intended locations.
-
-    // Verifies the left offsets of the widgets.
-    final e = find.text('E');
-    final r = find.text('R');
-    final o = find.text('O');
-
-    // Sees if each character is displayed in the correct order.
-    _expectAToBeFollowedByB(tester, e, r.at(0));
-    _expectAToBeFollowedByB(tester, r.at(0), r.at(1));
-    _expectAToBeFollowedByB(tester, r.at(1), o);
-    _expectAToBeFollowedByB(tester, o, r.at(2));
-  });
-}
-
-Future<void> _setUpErrorPage(
-    WidgetTester tester, double width, double height) async {
-  await tester.pumpWidget(MaterialApp(
-    home: Scaffold(
-      body: Container(
-        width: width,
-        height: height,
-        child: ErrorPage(),
-      ),
-    ),
-  ));
-}
-
-void _expectAToBeFollowedByB(WidgetTester tester, Finder a, Finder b) {
-  expect(tester.getTopLeft(a).dx < tester.getTopLeft(b).dx, true,
-      reason: 'Expected $a to be followed by $b when an error page created.');
-}
diff --git a/bin/simple_browser/test/widgets/history_buttons_test.dart b/bin/simple_browser/test/widgets/history_buttons_test.dart
deleted file mode 100644
index bab00c6..0000000
--- a/bin/simple_browser/test/widgets/history_buttons_test.dart
+++ /dev/null
@@ -1,164 +0,0 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-import 'package:flutter/material.dart';
-import 'package:flutter_test/flutter_test.dart';
-import 'package:fuchsia_logger/logger.dart';
-import 'package:mockito/mockito.dart';
-
-// ignore_for_file: implementation_imports
-import 'package:simple_browser/src/blocs/webpage_bloc.dart';
-import 'package:simple_browser/src/services/simple_browser_navigation_event_listener.dart';
-import 'package:simple_browser/src/services/simple_browser_web_service.dart';
-import 'package:simple_browser/src/widgets/history_buttons.dart';
-
-enum ButtonType {
-  back,
-  forward,
-  refresh,
-}
-
-void main() {
-  setupLogger(name: 'history_buttons_test');
-
-  late WebPageBloc webPageBloc;
-  late MockSimpleBrowserWebService mockWebService;
-  late MockSimpleBrowserNavigationEventListener mockEventListener;
-
-  ValueNotifier backStateNotifier = ValueNotifier<bool>(false);
-  ValueNotifier forwardStateNotifier = ValueNotifier<bool>(false);
-  ValueNotifier urlNotifier = ValueNotifier<String>('');
-  ValueNotifier pageTypeNotifier = ValueNotifier<PageType>(PageType.empty);
-
-  setUpAll(() {
-    mockEventListener = MockSimpleBrowserNavigationEventListener();
-    when(mockEventListener.backStateNotifier)
-        .thenAnswer((_) => backStateNotifier);
-    when(mockEventListener.forwardStateNotifier)
-        .thenAnswer((_) => forwardStateNotifier);
-    when(mockEventListener.urlNotifier).thenAnswer((_) => urlNotifier);
-
-    when(mockEventListener.backState)
-        .thenAnswer((_) => backStateNotifier.value);
-    when(mockEventListener.forwardState)
-        .thenAnswer((_) => forwardStateNotifier.value);
-    when(mockEventListener.pageType).thenAnswer((_) => pageTypeNotifier.value);
-
-    mockWebService = MockSimpleBrowserWebService();
-    when(mockWebService.navigationEventListener)
-        .thenAnswer((_) => mockEventListener);
-    webPageBloc = WebPageBloc(
-      webService: mockWebService,
-    );
-  });
-
-  testWidgets('There should be 3 text widgets: BCK, FWD, and RFRSH.',
-      (WidgetTester tester) async {
-    await _setUpHistoryButtons(tester, webPageBloc);
-
-    // Sees if there are a ‘BCK’, a 'FWD' and a 'RFRSH' texts.
-    expect(find.text('BCK'), findsOneWidget);
-    expect(find.text('FWD'), findsOneWidget);
-    expect(find.text('RFRSH'), findsOneWidget);
-  });
-
-  group('Buttons are all disabled', () {
-    testWidgets('A disalbed button should not work when tapped.',
-        (WidgetTester tester) async {
-      await _setUpHistoryButtons(tester, webPageBloc);
-
-      final historyButtons = _findHistoryButtons();
-
-      // Taps the back button and sees whether it works or not.
-      await _tapHistoryButton(tester, historyButtons, ButtonType.back);
-      _verifyAllNeverWork(webPageBloc);
-
-      // Taps the forward button and sees whether it works or not.
-      await _tapHistoryButton(tester, historyButtons, ButtonType.forward);
-      _verifyAllNeverWork(webPageBloc);
-
-      // Taps the refresh button and sees whether it works or not.
-      await _tapHistoryButton(tester, historyButtons, ButtonType.refresh);
-      _verifyAllNeverWork(webPageBloc);
-    });
-  });
-
-  group('Buttons are all enabled', () {
-    String testUrl;
-
-    // Set-ups for enabling the history buttons.
-    setUp(() {
-      testUrl = 'https://www.google.com';
-      backStateNotifier.value = true;
-      forwardStateNotifier.value = true;
-      urlNotifier.value = testUrl;
-      pageTypeNotifier.value = PageType.normal;
-    });
-
-    testWidgets('An enabled button should work when tapped.',
-        (WidgetTester tester) async {
-      await _setUpHistoryButtons(tester, webPageBloc);
-
-      final historyButtons = _findHistoryButtons();
-
-      // Taps the back button and sees whether it works or not.
-      await _tapHistoryButton(tester, historyButtons, ButtonType.back);
-      verify(webPageBloc.webService.goBack());
-      verifyNever(webPageBloc.webService.goForward());
-      verifyNever(webPageBloc.webService.refresh());
-
-      // Taps the forward button and sees whether it works or not.
-      await _tapHistoryButton(tester, historyButtons, ButtonType.forward);
-      verifyNever(webPageBloc.webService.goBack());
-      verify(webPageBloc.webService.goForward());
-      verifyNever(webPageBloc.webService.refresh());
-
-      // Taps the refresh button and sees whether it works or not.
-      await _tapHistoryButton(tester, historyButtons, ButtonType.refresh);
-      verifyNever(webPageBloc.webService.goBack());
-      verifyNever(webPageBloc.webService.goForward());
-      verify(webPageBloc.webService.refresh());
-    });
-  });
-}
-
-Future<void> _setUpHistoryButtons(
-  WidgetTester tester,
-  WebPageBloc bloc,
-) async {
-  await tester.pumpWidget(
-    MaterialApp(
-      home: Scaffold(
-        body: HistoryButtons(
-          bloc: bloc,
-        ),
-      ),
-    ),
-  );
-  await tester.pumpAndSettle();
-
-  expect(_findHistoryButtons(), findsNWidgets(3),
-      reason: 'Expected 3 history buttons on the HistoryButtons widget.');
-}
-
-Finder _findHistoryButtons() => find.byType(GestureDetector);
-
-Future<void> _tapHistoryButton(
-    WidgetTester tester, Finder buttons, ButtonType target) async {
-  int index = target.index;
-
-  await tester.tap(buttons.at(index));
-  await tester.pumpAndSettle();
-}
-
-void _verifyAllNeverWork(WebPageBloc bloc) {
-  verifyNever(bloc.webService.goBack());
-  verifyNever(bloc.webService.goForward());
-  verifyNever(bloc.webService.refresh());
-}
-
-class MockSimpleBrowserNavigationEventListener extends Mock
-    implements SimpleBrowserNavigationEventListener {}
-
-class MockSimpleBrowserWebService extends Mock
-    implements SimpleBrowserWebService {}
diff --git a/bin/simple_browser/test/widgets/navigation_bar_test.dart b/bin/simple_browser/test/widgets/navigation_bar_test.dart
deleted file mode 100644
index 6aeb048..0000000
--- a/bin/simple_browser/test/widgets/navigation_bar_test.dart
+++ /dev/null
@@ -1,170 +0,0 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'package:flutter/material.dart';
-import 'package:flutter_test/flutter_test.dart';
-import 'package:fuchsia_logger/logger.dart';
-import 'package:mockito/mockito.dart';
-
-// ignore_for_file: implementation_imports
-import 'package:simple_browser/src/blocs/tabs_bloc.dart';
-import 'package:simple_browser/src/blocs/webpage_bloc.dart';
-import 'package:simple_browser/src/models/tabs_action.dart';
-import 'package:simple_browser/src/services/simple_browser_navigation_event_listener.dart';
-import 'package:simple_browser/src/services/simple_browser_web_service.dart';
-import 'package:simple_browser/src/widgets/history_buttons.dart';
-import 'package:simple_browser/src/widgets/navigation_bar.dart';
-import 'package:simple_browser/src/widgets/navigation_field.dart';
-
-void main() {
-  setupLogger(name: 'navigation_bar_test');
-
-  WebPageBloc? webPageBloc;
-  MockSimpleBrowserWebService mockWebService;
-  MockSimpleBrowserNavigationEventListener mockEventListener;
-
-  ValueNotifier backStateNotifier = ValueNotifier<bool>(false);
-  ValueNotifier forwardStateNotifier = ValueNotifier<bool>(false);
-  ValueNotifier urlNotifier = ValueNotifier<String>('');
-  ValueNotifier pageTypeNotifier = ValueNotifier<PageType>(PageType.empty);
-  ValueNotifier isLoadedStateNotifier = ValueNotifier<bool>(true);
-
-  group('The bloc is null.', () {
-    testWidgets('Should create two empty containers and one + button.',
-        (WidgetTester tester) async {
-      await _setUpNavigationBar(tester, webPageBloc, () {});
-
-      const reasonSuffix =
-          'when the NavigationBar widget has a null webPageBloc.';
-
-      expect(find.byType(Container), findsNWidgets(3),
-          reason: 'Expected three containers $reasonSuffix');
-
-      // Sees if there are two empty Containers.
-      expect(
-          find.byWidgetPredicate(
-            (Widget widget) => widget is Container && widget.child == null,
-            description: 'Empty containers.',
-          ),
-          findsNWidgets(2),
-          reason: 'Expected two of those containers were empty $reasonSuffix');
-
-      // Sees if there are one + button.
-      expect(_findNewTabButton(), findsOneWidget,
-          reason:
-              'Expected one of those containers was a + button $reasonSuffix');
-    });
-  });
-
-  group('The bloc is not null.', () {
-    setUp(() {
-      mockEventListener = MockSimpleBrowserNavigationEventListener();
-      when(mockEventListener.backStateNotifier)
-          .thenAnswer((_) => backStateNotifier);
-      when(mockEventListener.forwardStateNotifier)
-          .thenAnswer((_) => forwardStateNotifier);
-      when(mockEventListener.urlNotifier).thenAnswer((_) => urlNotifier);
-      when(mockEventListener.isLoadedStateNotifier)
-          .thenAnswer((_) => isLoadedStateNotifier);
-
-      when(mockEventListener.backState)
-          .thenAnswer((_) => backStateNotifier.value);
-      when(mockEventListener.forwardState)
-          .thenAnswer((_) => forwardStateNotifier.value);
-      when(mockEventListener.pageType)
-          .thenAnswer((_) => pageTypeNotifier.value);
-      when(mockEventListener.isLoadedState)
-          .thenAnswer((_) => isLoadedStateNotifier.value);
-
-      mockWebService = MockSimpleBrowserWebService();
-      when(mockWebService.navigationEventListener)
-          .thenAnswer((_) => mockEventListener);
-      webPageBloc = WebPageBloc(
-        webService: mockWebService,
-      );
-    });
-
-    testWidgets(
-        'Should create one HistoryButtons, one URL field and one + button.',
-        (WidgetTester tester) async {
-      await _setUpNavigationBar(tester, webPageBloc, () {});
-
-      const reasonSuffix =
-          'when the NavigationBar widget has a non-null webPageBloc.';
-
-      expect(find.byType(HistoryButtons), findsOneWidget,
-          reason: 'Expected a HistoryButtons widget $reasonSuffix');
-      expect(find.byType(NavigationField), findsOneWidget,
-          reason: 'Expected a NavigationField widget $reasonSuffix');
-      expect(_findNewTabButton(), findsOneWidget,
-          reason: 'Expected a + button $reasonSuffix');
-    });
-
-    testWidgets('''Should show a progress bar when the page has not been loaded,
-        and should not show it anymore when the page loading is complete.''',
-        (WidgetTester tester) async {
-      await _setUpNavigationBar(tester, webPageBloc, () {});
-      isLoadedStateNotifier.value = false;
-      await tester.pump();
-      expect(find.byType(LinearProgressIndicator), findsOneWidget,
-          reason: 'Expected a progress bar when loading has not finished.');
-
-      isLoadedStateNotifier.value = true;
-      await tester.pumpAndSettle();
-      expect(find.byType(LinearProgressIndicator), findsNothing,
-          reason: 'Expected no progress bars when loading has completed.');
-    });
-
-    testWidgets('Should call the newTab callback when the + button is tapped.',
-        (WidgetTester tester) async {
-      TabsBloc tb = TabsBloc(
-        // TODO(https://fxbug.dev/71711): Figure out why `dart analyze`
-        // complains about this.
-        tabFactory: () => MockWebPageBloc(), // ignore: unnecessary_lambdas
-        disposeTab: (tab) => tab.dispose(),
-      );
-
-      await _setUpNavigationBar(
-          tester, webPageBloc, () => tb.request.add(NewTabAction()));
-
-      expect(tb.tabs.length, 0,
-          reason:
-              'Expected no tabs in the tabsBloc when none has been added to it.');
-
-      final newTabBtn = _findNewTabButton();
-      expect(newTabBtn, findsOneWidget,
-          reason: 'Expected one + button on the NavigationBar.');
-      await tester.tap(newTabBtn);
-      await tester.pumpAndSettle();
-      expect(tb.tabs.length, 1,
-          reason:
-              'Expected a tab in the tabsBloc when the + button was tapped.');
-    });
-  });
-}
-
-Future<void> _setUpNavigationBar(
-    WidgetTester tester, WebPageBloc? bloc, VoidCallback callback) async {
-  await tester.pumpWidget(
-    MaterialApp(
-      home: Scaffold(
-        body: BrowserNavigationBar(
-          bloc: bloc,
-          newTab: callback,
-          fieldFocus: FocusNode(),
-        ),
-      ),
-    ),
-  );
-}
-
-Finder _findNewTabButton() => find.byKey(Key('new_tab'));
-
-class MockSimpleBrowserNavigationEventListener extends Mock
-    implements SimpleBrowserNavigationEventListener {}
-
-class MockSimpleBrowserWebService extends Mock
-    implements SimpleBrowserWebService {}
-
-class MockWebPageBloc extends Mock implements WebPageBloc {}
diff --git a/bin/simple_browser/test/widgets/navigation_field_test.dart b/bin/simple_browser/test/widgets/navigation_field_test.dart
deleted file mode 100644
index 7f32cae..0000000
--- a/bin/simple_browser/test/widgets/navigation_field_test.dart
+++ /dev/null
@@ -1,117 +0,0 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-import 'package:flutter/material.dart';
-import 'package:flutter_test/flutter_test.dart';
-import 'package:fuchsia_logger/logger.dart';
-import 'package:mockito/mockito.dart';
-
-// ignore_for_file: implementation_imports
-import 'package:simple_browser/src/blocs/webpage_bloc.dart';
-import 'package:simple_browser/src/services/simple_browser_navigation_event_listener.dart';
-import 'package:simple_browser/src/services/simple_browser_web_service.dart';
-import 'package:simple_browser/src/widgets/navigation_field.dart';
-
-void main() {
-  setupLogger(name: 'navigation_field_test');
-
-  late SimpleBrowserWebService mockWebService;
-  late SimpleBrowserNavigationEventListener mockEventListener;
-  late WebPageBloc webPageBloc;
-
-  setUpAll(() {
-    mockWebService = MockSimpleBrowserWebService();
-    mockEventListener = MockSimpleBrowserNavigationEventListener();
-    webPageBloc = WebPageBloc(webService: mockWebService);
-  });
-
-  group('Default textfield (without URL)', () {
-    ValueNotifier urlNotifier = ValueNotifier<String>('');
-
-    setUp(() {
-      when(mockEventListener.urlNotifier).thenAnswer((_) => urlNotifier);
-      when(mockEventListener.url).thenAnswer((_) => urlNotifier.value);
-      when(mockWebService.navigationEventListener)
-          .thenAnswer((_) => mockEventListener);
-    });
-
-    const whenSuffix = 'when created it empty.';
-    testWidgets('Should focus on the textfield $whenSuffix',
-        (WidgetTester tester) async {
-      await _setUpNavigationField(tester, webPageBloc);
-
-      final textField = _findTextField();
-      expect(tester.widget<TextField>(textField).autofocus, true,
-          reason:
-              'Expected the TextField to be focused by default $whenSuffix');
-    });
-
-    testWidgets('Should call the callback when a valid url is entered.',
-        (WidgetTester tester) async {
-      await _setUpNavigationField(tester, webPageBloc);
-
-      String testUrl = 'https://www.google.com';
-      final textField = _findTextField();
-
-      // Enters the testUrl to the text field and submit it.
-      await tester.enterText(textField, testUrl);
-      await tester.testTextInput.receiveAction(TextInputAction.go);
-      await tester.pump();
-
-      // Sees if the corresponding callback is called.
-      verify(webPageBloc.webService.loadUrl(testUrl)).called(1);
-    });
-  });
-
-  group('Textfield with a URL', () {
-    ValueNotifier urlNotifier = ValueNotifier<String>('');
-
-    setUp(() {
-      when(mockEventListener.urlNotifier).thenAnswer((_) => urlNotifier);
-      when(mockEventListener.url).thenAnswer((_) => urlNotifier.value);
-      when(mockWebService.navigationEventListener)
-          .thenAnswer((_) => mockEventListener);
-    });
-
-    const whenSuffix = 'when created it with a url.';
-
-    testWidgets('Should not focus on the TextField $whenSuffix',
-        (WidgetTester tester) async {
-      urlNotifier.value = 'https://www.google.com';
-
-      await _setUpNavigationField(tester, webPageBloc);
-
-      final textField = _findTextField();
-      expect(tester.widget<TextField>(textField).autofocus, false,
-          reason: 'Expected the textfield not focused by default $whenSuffix');
-    });
-  });
-}
-
-Future<void> _setUpNavigationField(
-    WidgetTester tester, WebPageBloc bloc) async {
-  await tester.pumpWidget(
-    MaterialApp(
-      home: Scaffold(
-        body: NavigationField(
-          bloc: bloc,
-          focus: FocusNode(),
-        ),
-      ),
-    ),
-  );
-}
-
-Finder _findTextField() {
-  final textField = find.byType(TextField);
-  expect(textField, findsOneWidget,
-      reason: 'Expected a TextField on the NavigationField widget.');
-
-  return textField;
-}
-
-class MockSimpleBrowserNavigationEventListener extends Mock
-    implements SimpleBrowserNavigationEventListener {}
-
-class MockSimpleBrowserWebService extends Mock
-    implements SimpleBrowserWebService {}
diff --git a/bin/simple_browser/test/widgets/tabs_widget_test.dart b/bin/simple_browser/test/widgets/tabs_widget_test.dart
deleted file mode 100644
index abe20f0..0000000
--- a/bin/simple_browser/test/widgets/tabs_widget_test.dart
+++ /dev/null
@@ -1,633 +0,0 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-import 'dart:ui';
-
-import 'package:flutter/material.dart';
-import 'package:flutter_test/flutter_test.dart';
-import 'package:fuchsia_logger/logger.dart';
-import 'package:mockito/mockito.dart';
-
-// ignore_for_file: implementation_imports
-import 'package:simple_browser/src/blocs/tabs_bloc.dart';
-import 'package:simple_browser/src/blocs/webpage_bloc.dart';
-import 'package:simple_browser/src/models/tabs_action.dart';
-import 'package:simple_browser/src/services/simple_browser_navigation_event_listener.dart';
-import 'package:simple_browser/src/services/simple_browser_web_service.dart';
-import 'package:simple_browser/src/widgets/tabs_widget.dart';
-
-const _emptyTitle = 'NEW TAB';
-const screenWidth = 800.0;
-
-void main() {
-  setupLogger(name: 'tabs_widget_test');
-
-  late TabsBloc tabsBloc;
-  late SimpleBrowserWebService mockWebService;
-  late SimpleBrowserNavigationEventListener mockListener;
-  ValueNotifier titleNotifier = ValueNotifier<String>('');
-
-  setUp(() {
-    mockWebService = MockSimpleBrowserWebService();
-    mockListener = MockSimpleBrowserNavigationEventListener();
-
-    when(mockWebService.navigationEventListener)
-        .thenAnswer((_) => mockListener);
-    when(mockListener.pageTitleNotifier).thenAnswer((_) => titleNotifier);
-
-    tabsBloc = TabsBloc(
-      tabFactory: () => WebPageBloc(
-        webService: mockWebService,
-      ),
-      disposeTab: (tab) => tab.dispose(),
-    );
-  });
-
-  testWidgets('Should create tab widgets when more than two tabs added.',
-      (WidgetTester tester) async {
-    // Initial state: Tabsbloc does not have any tabs.
-    expect(tabsBloc.tabs.length, 0);
-    await _setUpTabsWidget(tester, tabsBloc);
-
-    // Sees if there is no tab widgets on the screen.
-    expect(_findNewTabWidgets(), findsNothing,
-        reason: 'Expected no tab widgets when no tabs added.');
-
-    // Adds one tab and sees if there is a tab in the tabsBloc, but still no tab widgets
-    // on the screen.
-    await _addNTabsToTabsBloc(tester, tabsBloc, 1);
-    expect(_findNewTabWidgets(), findsNothing,
-        reason: 'Expected no tab widgets when only 1 tab added.');
-
-    // Adds one more tab and sees if there are two tabs in the tabsBloc.
-    await _addNTabsToTabsBloc(tester, tabsBloc, 1);
-
-    // See if the tab widgets have the default title.
-    expect(_findNewTabWidgets(), findsNWidgets(2),
-        reason: '''Expected 2 tab widgets with the title, $_emptyTitle,
-        when 2 tabs added.''');
-  });
-
-  testWidgets(
-      'Should create tab widgets with a custom title when one is given.',
-      (WidgetTester tester) async {
-    // Gives a custom title, 'TAB_WIDGET_TEST'.
-    String expectedTitle = 'TAB_WIDGET_TEST';
-    when(mockListener.pageTitle).thenReturn(expectedTitle);
-
-    await _setUpTabsWidget(tester, tabsBloc);
-
-    // Adds two tabs since tab widgets are only created when there are more than one tab.
-    await _addNTabsToTabsBloc(tester, tabsBloc, 2);
-
-    // Sees if there are two tab widgets with the given title.
-    expect(find.text(expectedTitle), findsNWidgets(2),
-        reason: '''Expected 2 tab widgets with the title $expectedTitle
-        when 2 new tabs with it have been added.''');
-  });
-
-  testWidgets('''Should give the minimum width to the tab widgets
-      when the total sum of the tab widths is larger than the browser width.''',
-      (WidgetTester tester) async {
-    await _setUpTabsWidget(tester, tabsBloc);
-
-    // Adds seven tabs.
-    await _addNTabsToTabsBloc(tester, tabsBloc, 7);
-
-    // Sees if there are seven SizedBox widgets with the minimum width for the tab widgets.
-    // A tabsBloc should be wrapped by a SizedBox widget and have the minimum width
-    // when the total widths of the currently displayed tabs is larger than the browser width.
-    expect(_findMinTabWidgets(), findsNWidgets(7));
-  });
-
-  testWidgets('Should change the focus to it when an unfocused tab is tapped.',
-      (WidgetTester tester) async {
-    await _setUpTabsWidget(tester, tabsBloc);
-
-    await _addNTabsToTabsBloc(tester, tabsBloc, 3);
-
-    _verifyFocusedTabIndex(tabsBloc, 2);
-
-    final tabs = _findNewTabWidgets();
-    await tester.tap(tabs.at(0));
-    await tester.pumpAndSettle();
-
-    _verifyFocusedTabIndex(tabsBloc, 0);
-  });
-
-  testWidgets(
-      'Should show close buttons on the focused tab and a hovered tab if any.',
-      (WidgetTester tester) async {
-    await _setUpTabsWidget(tester, tabsBloc);
-    await _addNTabsToTabsBloc(tester, tabsBloc, 3);
-    final tabs = _findNewTabWidgets();
-    expect(tabs, findsNWidgets(3),
-        reason: 'Expected to find 3 tab widgets when 3 tabs added.');
-
-    // Sees if there is only one tab that has a close button on it.
-    expect(_findClose(), findsOneWidget,
-        reason: 'Expected to find 1 close button when hovering no tabs.');
-
-    // Configures a gesture.
-    final TestGesture gesture =
-        await tester.createGesture(kind: PointerDeviceKind.mouse);
-    addTearDown(gesture.removePointer);
-
-    // Mouse Enters onto the first tab.
-    final Offset firstTabCenter = tester.getCenter(tabs.at(0));
-    await gesture.moveTo(firstTabCenter);
-    await tester.pumpAndSettle();
-
-    // Sees if there are two tabs that have a close button on it.
-    expect(_findClose(), findsNWidgets(2),
-        reason: 'Expected to find 2 close buttons when hovering a tab.');
-
-    // Mouse is out from the first tab and moves to the currently focused tab.
-    final Offset focusedTabCenter =
-        tester.getCenter(tabs.at(tabsBloc.currentTabIdx!));
-    await gesture.moveTo(focusedTabCenter);
-    await tester.pumpAndSettle();
-
-    // Sees if there is only one tab that has a close button on it.
-    expect(_findClose(), findsOneWidget,
-        reason:
-            'Expected to find 1 close button when hovering the focused tab.');
-  });
-
-  testWidgets('Should close the tab when its close button is tapped.',
-      (WidgetTester tester) async {
-    await _setUpTabsWidget(tester, tabsBloc);
-    await _addNTabsToTabsBloc(tester, tabsBloc, 4);
-    expect(_findNewTabWidgets(), findsNWidgets(4),
-        reason: 'Expected to find 4 tab widgets when 4 tabs added.');
-    _verifyFocusedTabIndex(tabsBloc, 3);
-
-    Finder closeButtons = _findClose();
-    expect(closeButtons, findsOneWidget,
-        reason: 'Expected to find 1 close button when hovering no tabs.');
-
-    await tester.tap(closeButtons);
-    await tester.pumpAndSettle();
-    expect(tabsBloc.tabs.length, 3,
-        reason: 'Expected 3 tabs in tabsBloc after tapped the close.');
-    expect(_findNewTabWidgets(), findsNWidgets(3),
-        reason: 'Expected to find 3 tab widgets after tapped the close.');
-    _verifyFocusedTabIndex(tabsBloc, 2);
-  });
-
-  testWidgets(
-      'Should rearrange the tabs when a tab is dragged to another position',
-      (WidgetTester tester) async {
-    await _setUpTabsWidget(tester, tabsBloc);
-    await _addNTabsToTabsBloc(tester, tabsBloc, 5);
-    final tabs = _findNewTabWidgets();
-    expect(tabs, findsNWidgets(5),
-        reason: 'Expected to find 5 tab widgets when 5 tabs added.');
-    _verifyFocusedTabIndex(tabsBloc, 4);
-
-    WebPageBloc originalTab0 = tabsBloc.tabs[0];
-    WebPageBloc originalTab1 = tabsBloc.tabs[1];
-    WebPageBloc originalTab2 = tabsBloc.tabs[2];
-    WebPageBloc originalTab3 = tabsBloc.tabs[3];
-    WebPageBloc originalTab4 = tabsBloc.tabs[4];
-
-    // Drags the last tab 161.0 to the left.
-    await tester.drag(tabs.at(4), Offset(-161.0, 0.0));
-    await tester.pumpAndSettle();
-
-    // Sees if the tab just moved is focused.
-    expect(tabsBloc.currentTabIdx, 3,
-        reason: '''Expected the index of the focused tab to be rearranged to 3,
-            but actually has been rearranged to ${tabsBloc.currentTabIdx}.''');
-
-    // Sees if all tabs have been rarranged correctly.
-    expect(tabsBloc.tabs[0], originalTab0,
-        reason: 'Expected that the 1st tab used to be the 1st.');
-    expect(tabsBloc.tabs[1], originalTab1,
-        reason: 'Expected that the 2nd tab used to be the 2nd.');
-    expect(tabsBloc.tabs[2], originalTab2,
-        reason: 'Expected that the 3rd tab used to be the 3rd.');
-    expect(tabsBloc.tabs[3], originalTab4,
-        reason: 'Expected that the 4th tab used to be the 5th.');
-
-    expect(tabsBloc.tabs[4], originalTab3,
-        reason: 'Expected that the 5th tab used to the 4th.');
-
-    // Drags the 2nd tab 170.0 to the right.
-    await tester.drag(tabs.at(1), Offset(161.0, 0.0));
-    await tester.pumpAndSettle();
-
-    // Sees if the tab just moved is focused.
-    _verifyFocusedTabIndex(tabsBloc, 2);
-
-    // Sees if all tabs have been rarranged correctly.
-    expect(tabsBloc.tabs[0], originalTab0,
-        reason: 'Expected that the 1st tab used to be the 1st.');
-    expect(tabsBloc.tabs[1], originalTab2,
-        reason: 'Expected that the 2nd tab used to be the 3rd.');
-    expect(tabsBloc.tabs[2], originalTab1,
-        reason: 'Expected that the 3rd tab used to be the 2nd.');
-    expect(tabsBloc.tabs[3], originalTab4,
-        reason: 'Expected that the 4th tab used to be the 5th.');
-    expect(tabsBloc.tabs[4], originalTab3,
-        reason: 'Expected that the 5th tab used to the 4th.');
-  });
-
-  group('Scrollable tab list', () {
-    final rightScrollMargin =
-        screenWidth - kTabBarHeight - kScrollToMargin - kMinTabWidth;
-    final rightMinMargin = screenWidth - kTabBarHeight - kMinTabWidth;
-    final leftScrollMargin = kScrollToMargin + kTabBarHeight;
-    final leftMinMargin = kTabBarHeight;
-
-    testWidgets('The tab widget list should scroll on a scroll button tapped.',
-        (WidgetTester tester) async {
-      await _setUpTabsWidget(tester, tabsBloc);
-
-      // Creates 8 tabs.
-      await _addNTabsToTabsBloc(tester, tabsBloc, 10);
-
-      // The expected display of the initial tab list (*currently focused tab):
-      // |-0-||-1-||-2-||-3-||-4-||-5-||-6-||-7-||-8-||-9*-|
-      //                   |-            SCREEN           -|
-
-      final tabs = _findMinTabWidgets();
-
-      // Sees if there are 8 tab widgets created.
-      expect(tabs, findsNWidgets(10),
-          reason: 'Expected to find 10 tabs, but actually $tabs were found.');
-
-      // Sees if the viewport of the list is on the right.
-      _verifyFocusedTabIndex(tabsBloc, 9);
-      _verifyFocusedTabMargin(tester, tabs, rightMinMargin);
-      _verifyPartlyOffscreenFromLeft(tester, tabs.at(3));
-
-      final leftScrollButton = find.byIcon(Icons.keyboard_arrow_left);
-      expect(leftScrollButton, findsOneWidget);
-
-      final rightScrollButton = find.byIcon(Icons.keyboard_arrow_right);
-      expect(rightScrollButton, findsOneWidget);
-
-      // Taps on the left scroll button.
-      await tester.tap(leftScrollButton);
-      await tester.pumpAndSettle();
-
-      // The expected display of the tab list (*currently focused tab):
-      // |-0-||-1-||-2-||-3-||-4-||-5-||-6-||-7-||-8-||-9*-|
-      //  |-            SCREEN           -|
-
-      // Sees if the list has been scrolled to the left.
-      _verifyPartlyOffscreenFromLeft(tester, tabs.first);
-      _verifyEntirelyOffscreenFromRight(tester, tabs.last);
-      _verifyPartlyOffscreenFromRight(tester, tabs.at(6));
-
-      await tester.tap(leftScrollButton);
-      await tester.pumpAndSettle();
-
-      // The expected display of the tab list (*currently focused tab):
-      // |-0-||-1-||-2-||-3-||-4-||-5-||-6-||-7-||-8-||-9*-|
-      // |-            SCREEN           -|
-
-      // Sees if the list has been scrolled to the left end.
-      final firstTabLeftX = tester.getTopLeft(tabs.first).dx;
-      expect(firstTabLeftX, leftMinMargin,
-          reason: '''Expected the scroll list have been scroll to its far left,
-          but actually it has not.''');
-
-      // Taps on the right scroll button.
-      await tester.tap(rightScrollButton);
-      await tester.pumpAndSettle();
-
-      // The expected display of the tab list (*currently focused tab):
-      // |-0-||-1-||-2-||-3-||-4-||-5-||-6-||-7-||-8-||-9*-|
-      //                  |-            SCREEN           -|
-
-      // Sees if the list has been scrolled to the right.
-      _verifyPartlyOffscreenFromLeft(tester, tabs.at(3));
-      _verifyPartlyOffscreenFromRight(tester, tabs.last);
-
-      await tester.tap(rightScrollButton);
-      await tester.pumpAndSettle();
-
-      // The expected display of the tab list (*currently focused tab):
-      // |-0-||-1-||-2-||-3-||-4-||-5-||-6-||-7-||-8-||-9*-|
-      //                   |-            SCREEN           -|
-
-      // Sees if the list has been scrolled to the right end.
-      _verifyFocusedTabMargin(tester, tabs, rightMinMargin);
-      _verifyPartlyOffscreenFromLeft(tester, tabs.at(3));
-    });
-
-    testWidgets(
-        'The tab list should scroll to the left when the moving tab hits its left edge.',
-        (WidgetTester tester) async {
-      await _setUpTabsWidget(tester, tabsBloc);
-
-      const numTabs = 8;
-
-      // Creates 8 tabs.
-      await _addNTabsToTabsBloc(tester, tabsBloc, numTabs);
-
-      // The expected display of the initial tab list (*currently focused tab):
-      // |- 0 -| |- 1 -| |- 2 -| |- 3 -| |- 4 -| |- 5 -| |- 6 -| |- 7* -|
-      //              |-                     SCREEN                    -|
-
-      final tabs = _findMinTabWidgets();
-      _verifyFocusedTabIndex(tabsBloc, numTabs - 1);
-
-      // Saves the original tab positions.
-      final originalXs = <double>[];
-      for (int i = 0; i < numTabs; i++) {
-        originalXs.add(tester.getTopLeft(tabs.at(i)).dx);
-      }
-
-      // Drags the 3rd tab 35.0 to the left to hit the left edge of the list.
-      const tabIndexToDrag = 2;
-      await tester.drag(tabs.at(tabIndexToDrag), Offset(-35.0, 0.0));
-      await tester.pumpAndSettle();
-
-      // The expected display of the tab list (*currently focused tab):
-      // |- 0 -| |- 1 -| |- 2* -| |- 3 -| |- 4 -| |- 5 -| |- 6 -| |- 7 -|
-      //             |-                     SCREEN                    -|
-
-      _verifyFocusedTabIndex(tabsBloc, tabIndexToDrag);
-      final afterDragXs = <double>[];
-
-      // Saves the new tab positions to a list in the actual tab order.
-      for (int i = 0; i < tabIndexToDrag; i++) {
-        afterDragXs.add(tester.getTopLeft(tabs.at(i)).dx);
-      }
-      afterDragXs.add(tester.getTopLeft(tabs.last).dx);
-      for (int i = tabIndexToDrag; i < numTabs - 1; i++) {
-        afterDragXs.add(tester.getTopLeft(tabs.at(i)).dx);
-      }
-
-      // Sees if the list has auto-scrolled to the left by comparing
-      // the tab positions before and after the 3rd tab hit the left edge.
-      for (int i = 0; i < numTabs; i++) {
-        expect(afterDragXs[i] > originalXs[i], true);
-      }
-    });
-
-    testWidgets(
-        'The tab list should scroll to the right when the moving tab hits its right edge.',
-        (WidgetTester tester) async {
-      await _setUpTabsWidget(tester, tabsBloc);
-
-      const numTabs = 8;
-
-      // Creates 8 tabs.
-      await _addNTabsToTabsBloc(tester, tabsBloc, numTabs);
-
-      // The expected display of the initial tab list (*currently focused tab):
-      // |- 0 -| |- 1 -| |- 2 -| |- 3 -| |- 4 -| |- 5 -| |- 6 -| |- 7* -|
-      //              |-                     SCREEN                    -|
-
-      final tabs = _findMinTabWidgets();
-      _verifyFocusedTabIndex(tabsBloc, numTabs - 1);
-
-      _verifyPartlyOffscreenFromLeft(tester, tabs.at(1));
-
-      final leftScrollButton = find.byIcon(Icons.keyboard_arrow_left);
-      expect(leftScrollButton, findsOneWidget);
-
-      // Taps on the left scroll button.
-      await tester.tap(leftScrollButton);
-      await tester.pumpAndSettle();
-
-      // The expected display of the tab list (*currently focused tab):
-      // |- 0 -| |- 1 -| |- 2* -| |- 3 -| |- 4 -| |- 5 -| |- 6 -| |- 7 -|
-      // |-                     SCREEN                    -|
-
-      _verifyPartlyOffscreenFromRight(tester, tabs.at(6));
-      _verifyEntirelyOffscreenFromRight(tester, tabs.last);
-
-      // Saves the original tab positions.
-      final originalXs = <double>[];
-      for (int i = 0; i < numTabs; i++) {
-        originalXs.add(tester.getTopLeft(tabs.at(i)).dx);
-      }
-
-      // Drags the 6th tab 35.0 to the right to hit the right edge of the list.
-      const tabIndexToDrag = 5;
-      await tester.drag(tabs.at(tabIndexToDrag), Offset(35.0, 0.0));
-      await tester.pumpAndSettle();
-
-      // The expected display of the tab list (*currently focused tab):
-      // |- 0 -| |- 1 -| |- 2 -| |- 3 -| |- 4 -| |- 5* -| |- 6 -| |- 7 -|
-      //  |-                     SCREEN                    -|
-
-      _verifyFocusedTabIndex(tabsBloc, tabIndexToDrag);
-
-      final afterDragXs = <double>[];
-
-      // Saves the new tab positions to a list in the actual tab order.
-      for (int i = 0; i < tabIndexToDrag; i++) {
-        afterDragXs.add(tester.getTopLeft(tabs.at(i)).dx);
-      }
-      afterDragXs.add(tester.getTopLeft(tabs.last).dx);
-      for (int i = tabIndexToDrag; i < numTabs - 1; i++) {
-        afterDragXs.add(tester.getTopLeft(tabs.at(i)).dx);
-      }
-
-      // Sees if the list has auto-scrolled to the right by comparing
-      // the tab positions before and after the 6th tab hit the right edge.
-      for (int i = 0; i < numTabs; i++) {
-        expect(afterDragXs[i] < originalXs[i], true);
-      }
-    });
-
-    testWidgets(
-        'The tab widget list should scroll if needed depending on the offset of the focused tab.',
-        (WidgetTester tester) async {
-      await _setUpTabsWidget(tester, tabsBloc);
-
-      // Creates 8 tabs.
-      await _addNTabsToTabsBloc(tester, tabsBloc, 8);
-
-      // The expected display of the initial tab list (*currently focused tab):
-      // |- 0 -| |- 1 -| |- 2 -| |- 3 -| |- 4 -| |- 5 -| |- 6 -| |- 7* -|
-      //              |-                     SCREEN                    -|
-
-      final tabs = _findMinTabWidgets();
-
-      // Sees if there are 8 tab widgets created.
-      expect(tabs, findsNWidgets(8),
-          reason: 'Expected to find 8 tabs, but actually $tabs were found.');
-
-      // Sees if the expected tab is currently focused and its at the desired
-      // position.
-      _verifyFocusedTabIndex(tabsBloc, 7);
-      _verifyFocusedTabMargin(tester, tabs, rightMinMargin);
-
-      // Sees if certain tabs are partly/entire offscreen.
-      _verifyEntirelyOffscreenFromLeft(tester, tabs.first);
-      _verifyPartlyOffscreenFromLeft(tester, tabs.at(1));
-
-      // Finds the fifth tab and checks its current position.
-      final fifthTab = tabs.at(4);
-      final expectedFifthTabLeftX = tester.getTopLeft(fifthTab).dx;
-
-      // Taps on the fifth tab to change the focus.
-      await tester.tap(fifthTab);
-      await tester.pumpAndSettle();
-
-      // The expected display of the tab list (*currently focused tab):
-      // |- 0 -| |- 1 -| |- 2 -| |- 3 -| |- 4* -| |- 5 -| |- 6 -| |- 7 -|
-      //              |-                     SCREEN                    -|
-
-      _verifyFocusedTabIndex(tabsBloc, 4);
-      _verifyFocusedTabMargin(tester, tabs, expectedFifthTabLeftX);
-
-      // Focuses on the second tab by directly adding the FocusTabAction to the
-      // tabsBloc since tester.tap() does not work on the widget whose center
-      // is offscreen.
-      tabsBloc.request.add(FocusTabAction(tab: tabsBloc.tabs[1]));
-      await tester.pumpAndSettle();
-
-      // The expected display of the tab list (*currently focused tab):
-      // |- 0 -| |- 1* -| |- 2 -| |- 3 -| |- 4 -| |- 5 -| |- 6 -| |- 7 -|
-      //     |-                     SCREEN                    -|
-
-      _verifyFocusedTabIndex(tabsBloc, 1);
-      _verifyFocusedTabMargin(tester, tabs, leftScrollMargin);
-
-      _verifyPartlyOffscreenFromLeft(tester, tabs.first);
-      _verifyPartlyOffscreenFromRight(tester, tabs.at(5));
-      _verifyEntirelyOffscreenFromRight(tester, tabs.at(6));
-
-      // Focuses on the first tab.
-      tabsBloc.request.add(FocusTabAction(tab: tabsBloc.tabs[0]));
-      await tester.pumpAndSettle();
-
-      // The expected display of the tab list (*currently focused tab):
-      // |- 0* -| |- 1 -| |- 2 -| |- 3 -| |- 4 -| |- 5 -| |- 6 -| |- 7 -|
-      // |-                     SCREEN                    -|
-
-      _verifyFocusedTabIndex(tabsBloc, 0);
-      _verifyFocusedTabMargin(tester, tabs, leftMinMargin);
-
-      _verifyPartlyOffscreenFromRight(tester, tabs.at(5));
-      _verifyEntirelyOffscreenFromRight(tester, tabs.at(6));
-
-      // Focuses on the seventh tab.
-      tabsBloc.request.add(FocusTabAction(tab: tabsBloc.tabs[6]));
-      await tester.pumpAndSettle();
-
-      // The expected display of the tab list (*currently focused tab):
-      // |- 0 -| |- 1 -| |- 2 -| |- 3 -| |- 4 -| |- 5 -| |- 6* -| |- 7 -|
-      //          |-                     SCREEN                    -|
-
-      _verifyFocusedTabIndex(tabsBloc, 6);
-      _verifyFocusedTabMargin(tester, tabs, rightScrollMargin);
-
-      _verifyEntirelyOffscreenFromLeft(tester, tabs.first);
-      _verifyPartlyOffscreenFromLeft(tester, tabs.at(1));
-      _verifyPartlyOffscreenFromRight(tester, tabs.at(6));
-    });
-  });
-}
-
-Future<void> _setUpTabsWidget(WidgetTester tester, TabsBloc tabsBloc) async {
-  await tester.pumpWidget(MaterialApp(
-    home: Scaffold(
-      body: Container(
-        width: screenWidth,
-        child: TabsWidget(
-          bloc: tabsBloc,
-        ),
-      ),
-    ),
-  ));
-
-  await tester.pumpAndSettle();
-}
-
-Future<void> _addNTabsToTabsBloc(
-    WidgetTester tester, TabsBloc tabsBloc, int n) async {
-  int currentNumTabs = tabsBloc.tabs.length;
-  for (int i = 0; i < n; i++) {
-    tabsBloc.request.add(NewTabAction());
-    await tester.pumpAndSettle();
-  }
-  expect(tabsBloc.tabs.length, currentNumTabs + n);
-}
-
-Finder _findMinTabWidgets() => find.byWidgetPredicate((Widget widget) {
-      if (widget is Container && widget.key == Key('tab')) {
-        BoxConstraints width = widget.constraints!.widthConstraints();
-        return (width.minWidth == width.maxWidth) &&
-            (width.minWidth == kMinTabWidth);
-      }
-      return false;
-    });
-
-Finder _findNewTabWidgets() => find.text(_emptyTitle);
-
-Finder _findClose() => find.byIcon(Icons.clear);
-
-// Verifies if the currently focused tab is the expected tab.
-void _verifyFocusedTabIndex(TabsBloc tb, int expectedIndex) {
-  expect(tb.currentTabIdx, expectedIndex,
-      reason: '''Expected the index of the currently focused tab to be
-      $expectedIndex, but actually is ${tb.currentTabIdx}''');
-}
-
-// Verifies if the currently focused tab has the expected left margin.
-void _verifyFocusedTabMargin(
-    WidgetTester tester, Finder tabs, double expectedMargin) {
-  // The currently focused tab always become the last member of the finder
-  // since it is rendered on the top of the other tabs.
-  final actualMargin = tester.getTopLeft(tabs.last).dx;
-  expect(actualMargin, expectedMargin,
-      reason: '''Expected the left margin to the currently focused tab to be
-        $expectedMargin, but is actually $actualMargin.''');
-}
-
-void _verifyPartlyOffscreenFromLeft(WidgetTester tester, Finder tab) {
-  final leftBorderX = kTabBarHeight;
-  final tabLeftX = tester.getTopLeft(tab).dx;
-  final tabRightX = tester.getTopRight(tab).dx;
-  expect(tabLeftX < leftBorderX, true,
-      reason: '''Expected this tab's left edge to be offscreen,
-          but actually is onscreen.''');
-  expect(tabRightX >= leftBorderX, true,
-      reason: '''Expected this tab's right edge to be onscreen,
-      but actually is offscreen.''');
-}
-
-void _verifyEntirelyOffscreenFromLeft(WidgetTester tester, Finder tab) {
-  final leftBorderX = kTabBarHeight;
-  final tabRightX = tester.getTopRight(tab).dx;
-  expect(tabRightX < leftBorderX, true,
-      reason: '''Expected this tab's right edge to be offscreen,
-      but actually is onscreen.''');
-}
-
-void _verifyPartlyOffscreenFromRight(WidgetTester tester, Finder tab) {
-  final rightBorderX = screenWidth - kTabBarHeight;
-  final tabLeftX = tester.getTopLeft(tab).dx;
-  final tabRightX = tester.getTopRight(tab).dx;
-  expect(tabLeftX < rightBorderX, true,
-      reason: '''Expected this tab's left edge to be onscreen,
-          but actually is offscreen.''');
-  expect(tabRightX >= rightBorderX, true,
-      reason: '''Expected this tab's right edge to be offscreen,
-      but actually is onscreen.''');
-}
-
-void _verifyEntirelyOffscreenFromRight(WidgetTester tester, Finder tab) {
-  final rightBorderX = screenWidth - kTabBarHeight;
-  final tabLeftX = tester.getTopLeft(tab).dx;
-  expect(tabLeftX > rightBorderX, true,
-      reason: '''Expected this tab's left edge to be offscreen,
-      but actually is onscreen.''');
-}
-
-class MockSimpleBrowserNavigationEventListener extends Mock
-    implements SimpleBrowserNavigationEventListener {}
-
-class MockSimpleBrowserWebService extends Mock
-    implements SimpleBrowserWebService {}
-
-class MockWebPageBloc extends Mock implements WebPageBloc {}
diff --git a/bin/simple_browser_internationalization/BUILD.gn b/bin/simple_browser_internationalization/BUILD.gn
deleted file mode 100644
index 20f985d..0000000
--- a/bin/simple_browser_internationalization/BUILD.gn
+++ /dev/null
@@ -1,36 +0,0 @@
-# Copyright 2019 The Fuchsia Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import("//build/dart/dart_library.gni")
-
-dart_library("internationalization") {
-  package_name = "internationalization"
-  null_safe = true
-
-  # Disabling analysis for the time being, this is mostly generated code to begin
-  # with, and there are issues like this one that make things harder than necessary:
-  # https://github.com/dart-lang/sdk/issues/38598.
-  disable_analysis = true
-
-  sources = [
-    "localization/messages_all.dart",
-    "localization/messages_ar-XB.dart",
-    "localization/messages_en-XA.dart",
-    "localization/messages_en-XC.dart",
-    "localization/messages_nl.dart",
-    "localization/messages_sr-Latn.dart",
-    "localization/messages_sr.dart",
-    "localizations_delegate.dart",
-    "strings.dart",
-    "supported_locales.dart",
-  ]
-
-  deps = [
-    "//sdk/dart/fuchsia_internationalization_flutter",
-    "//sdk/dart/fuchsia_logger",
-    "//third_party/dart-pkg/git/flutter/packages/flutter",
-    "//third_party/dart-pkg/git/flutter/packages/flutter_localizations",
-    "//third_party/dart-pkg/pub/intl",
-  ]
-}
diff --git a/bin/simple_browser_internationalization/README.md b/bin/simple_browser_internationalization/README.md
deleted file mode 100644
index f30004e..0000000
--- a/bin/simple_browser_internationalization/README.md
+++ /dev/null
@@ -1,42 +0,0 @@
-# `simple_browser` internationalization and localization
-
-This Dart package contains the support for `simple_browser` localization and
-internationalization.
-
-At its current state the support is in its very early phase.  There is a lot of
-manual scaffolding that could be replaced by auto-generating the code of
-interest.  Doing so will be the topic of followup work.
-
-# How to use this package
-
-This package is purpose-built for the `simple_browser`.  The central part is
-the file `lib/strings.dart` which by design contains all the strings that the
-simple browser needs to vary based on the system locale.
-
-Add a function to this file whenever you need to introduce a new text message.
-
-## Translating
-
-The translation process amounts to making ARB files for each of the supported
-locales of interest and providing the translations for the messages and
-strings.  The basic tool for doing so would be a simple text editor. While
-there also exists a wealth of tools that help software translators, it is out
-of scope of this file to go into specific details.  So the choice of the
-translation environment is left to the developer and translator.
-
-## Generating Dart runtime format for the translations
-
-Once translated you can use the file `./scripts/run_generate_from_arb.sh` from
-`fuchsia_internationalization` library to generate the Dart code that wires up
-the translation.  You will need to rebuild the entire project to take the new
-translations in.
-
-# Further reading
-
-Internationalization and localization are topic unto themselves, and it is out
-of scope of this file to go into all the details.  Please see the section
-on [Internationalizing Flutter apps][1] for many more details that are directly
-relevant to Dart and Flutter app localization.
-
-[1]: https://flutter.dev/docs/development/accessibility-and-localization/internationalization
-[arb]: https://github.com/google/app-resource-bundle
diff --git a/bin/simple_browser_internationalization/lib/localization/messages_all.dart b/bin/simple_browser_internationalization/lib/localization/messages_all.dart
deleted file mode 100755
index 4bfe09a..0000000
--- a/bin/simple_browser_internationalization/lib/localization/messages_all.dart
+++ /dev/null
@@ -1,140 +0,0 @@
-// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
-// This is a library that looks up messages for specific locales by
-// delegating to the appropriate library.
-
-// Ignore issues from commonly used lints in this file.
-// ignore_for_file:implementation_imports, file_names, unnecessary_new
-// ignore_for_file:unnecessary_brace_in_string_interps, directives_ordering
-// ignore_for_file:argument_type_not_assignable, invalid_assignment
-// ignore_for_file:prefer_single_quotes, prefer_generic_function_type_aliases
-// ignore_for_file:comment_references
-
-import 'dart:async';
-
-import 'package:intl/intl.dart';
-import 'package:intl/message_lookup_by_library.dart';
-import 'package:intl/src/intl_helpers.dart';
-
-import 'messages_ar-XB.dart' deferred as messages_ar_xb;
-import 'messages_en-XA.dart' deferred as messages_en_xa;
-import 'messages_en-XC.dart' deferred as messages_en_xc;
-import 'messages_nl.dart' deferred as messages_nl;
-import 'messages_sr-Latn.dart' deferred as messages_sr_latn;
-import 'messages_sr.dart' deferred as messages_sr;
-
-typedef Future<dynamic> LibraryLoader();
-Map<String, LibraryLoader> _deferredLibraries = {
-  'ar_XB': messages_ar_xb.loadLibrary,
-  'en_XA': messages_en_xa.loadLibrary,
-  'en_XC': messages_en_xc.loadLibrary,
-  'nl': messages_nl.loadLibrary,
-  'sr_Latn': messages_sr_latn.loadLibrary,
-  'sr': messages_sr.loadLibrary,
-};
-
-MessageLookupByLibrary? _findExact(String localeName) {
-  switch (localeName) {
-    case 'ar_XB':
-      return messages_ar_xb.messages;
-    case 'en_XA':
-      return messages_en_xa.messages;
-    case 'en_XC':
-      return messages_en_xc.messages;
-    case 'nl':
-      return messages_nl.messages;
-    case 'sr_Latn':
-      return messages_sr_latn.messages;
-    case 'sr':
-      return messages_sr.messages;
-    default:
-      return null;
-  }
-}
-
-/// User programs should call this before using [localeName] for messages.
-Future<bool> initializeMessages(String localeName) async {
-  var availableLocale = Intl.verifiedLocale(
-      localeName, (locale) => _deferredLibraries[locale] != null,
-      onFailure: (_) => null);
-  if (availableLocale == null) {
-    return new Future.value(false);
-  }
-  var lib = _deferredLibraries[availableLocale];
-  await (lib == null ? new Future.value(false) : lib());
-  initializeInternalMessageLookup(() => new CompositeMessageLookup());
-  messageLookup.addLocale(availableLocale, _findGeneratedMessagesFor);
-  return new Future.value(true);
-}
-
-bool _messagesExistFor(String locale) {
-  try {
-    return _findExact(locale) != null;
-  } catch (e) {
-    return false;
-  }
-}
-
-MessageLookupByLibrary? _findGeneratedMessagesFor(String locale) {
-  var actualLocale =
-      Intl.verifiedLocale(locale, _messagesExistFor, onFailure: (_) => null);
-  if (actualLocale == null) return null;
-  return _findExact(actualLocale);
-}
-
-/// Turn the JSON template into a string.
-///
-/// We expect one of the following forms for the template.
-/// * null -> null
-/// * String s -> s
-/// * int n -> '${args[n]}'
-/// * List list, one of
-///   * ['Intl.plural', int howMany, (templates for zero, one, ...)]
-///   * ['Intl.gender', String gender, (templates for female, male, other)]
-///   * ['Intl.select', String choice, { 'case' : template, ...} ]
-///   * ['text alternating with ', 0 , ' indexes in the argument list']
-String? evaluateJsonTemplate(dynamic input, List<dynamic> args) {
-  if (input == null) return null;
-  if (input is String) return input;
-  if (input is int) {
-    return "${args[input]}";
-  }
-
-  List<dynamic> template = input;
-  var messageName = template.first;
-  if (messageName == "Intl.plural") {
-    var howMany = args[template[1]];
-    return evaluateJsonTemplate(
-        Intl.pluralLogic(howMany,
-            zero: template[2],
-            one: template[3],
-            two: template[4],
-            few: template[5],
-            many: template[6],
-            other: template[7]),
-        args);
-  }
-  if (messageName == "Intl.gender") {
-    var gender = args[template[1]];
-    return evaluateJsonTemplate(
-        Intl.genderLogic(gender,
-            female: template[2], male: template[3], other: template[4]),
-        args);
-  }
-  if (messageName == "Intl.select") {
-    var select = args[template[1]];
-    var choices = template[2];
-    return evaluateJsonTemplate(Intl.selectLogic(select, choices), args);
-  }
-
-  // If we get this far, then we are a basic interpolation, just strings and
-  // ints.
-  var output = StringBuffer();
-  for (var entry in template) {
-    if (entry is int) {
-      output.write("${args[entry]}");
-    } else {
-      output.write("$entry");
-    }
-  }
-  return output.toString();
-}
diff --git a/bin/simple_browser_internationalization/lib/localization/messages_ar-XB.dart b/bin/simple_browser_internationalization/lib/localization/messages_ar-XB.dart
deleted file mode 100755
index 9e206d0..0000000
--- a/bin/simple_browser_internationalization/lib/localization/messages_ar-XB.dart
+++ /dev/null
@@ -1,33 +0,0 @@
-// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
-// This is a library that provides messages for a ar_XB locale. All the
-// messages from the main program should be duplicated here with the same
-// function name.
-
-// Ignore issues from commonly used lints in this file.
-// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new
-// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
-// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
-// ignore_for_file:unused_import, file_names
-
-import 'package:intl/intl.dart';
-import 'package:intl/message_lookup_by_library.dart';
-import 'dart:convert';
-import 'messages_all.dart' show evaluateJsonTemplate;
-
-final messages = new MessageLookup();
-
-typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
-
-class MessageLookup extends MessageLookupByLibrary {
-  String get localeName => 'ar_XB';
-
-  String? evaluateMessage(translation, List<dynamic> args) {
-    return evaluateJsonTemplate(translation, args);
-  }
-
-  var _messages;
-  get messages => _messages ??=
-      const JsonDecoder().convert(messageText) as Map<String, dynamic>;
-  static final messageText = r'''
-{"back":"‏‮Bck‬‏","browser":"‏‮Browser‬‏","forward":"‏‮Fwd‬‏","refresh":"‏‮Rfrsh‬‏","search":"‏‮Search‬‏"}''';
-}
diff --git a/bin/simple_browser_internationalization/lib/localization/messages_en-XA.dart b/bin/simple_browser_internationalization/lib/localization/messages_en-XA.dart
deleted file mode 100755
index 78335bc..0000000
--- a/bin/simple_browser_internationalization/lib/localization/messages_en-XA.dart
+++ /dev/null
@@ -1,33 +0,0 @@
-// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
-// This is a library that provides messages for a en_XA locale. All the
-// messages from the main program should be duplicated here with the same
-// function name.
-
-// Ignore issues from commonly used lints in this file.
-// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new
-// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
-// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
-// ignore_for_file:unused_import, file_names
-
-import 'package:intl/intl.dart';
-import 'package:intl/message_lookup_by_library.dart';
-import 'dart:convert';
-import 'messages_all.dart' show evaluateJsonTemplate;
-
-final messages = new MessageLookup();
-
-typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
-
-class MessageLookup extends MessageLookupByLibrary {
-  String get localeName => 'en_XA';
-
-  String? evaluateMessage(translation, List<dynamic> args) {
-    return evaluateJsonTemplate(translation, args);
-  }
-
-  var _messages;
-  get messages => _messages ??=
-      const JsonDecoder().convert(messageText) as Map<String, dynamic>;
-  static final messageText = r'''
-{"back":"[Бçķ one]","browser":"[Бŕöŵšéŕ one]","forward":"[Fŵð one]","refresh":"[Ŕƒŕšĥ one]","search":"[Šéåŕçĥ one]"}''';
-}
diff --git a/bin/simple_browser_internationalization/lib/localization/messages_en-XC.dart b/bin/simple_browser_internationalization/lib/localization/messages_en-XC.dart
deleted file mode 100755
index c9e9d98..0000000
--- a/bin/simple_browser_internationalization/lib/localization/messages_en-XC.dart
+++ /dev/null
@@ -1,33 +0,0 @@
-// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
-// This is a library that provides messages for a en_XC locale. All the
-// messages from the main program should be duplicated here with the same
-// function name.
-
-// Ignore issues from commonly used lints in this file.
-// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new
-// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
-// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
-// ignore_for_file:unused_import, file_names
-
-import 'package:intl/intl.dart';
-import 'package:intl/message_lookup_by_library.dart';
-import 'dart:convert';
-import 'messages_all.dart' show evaluateJsonTemplate;
-
-final messages = new MessageLookup();
-
-typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
-
-class MessageLookup extends MessageLookupByLibrary {
-  String get localeName => 'en_XC';
-
-  String? evaluateMessage(translation, List<dynamic> args) {
-    return evaluateJsonTemplate(translation, args);
-  }
-
-  var _messages;
-  get messages => _messages ??=
-      const JsonDecoder().convert(messageText) as Map<String, dynamic>;
-  static final messageText = r'''
-{"back":"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‎‎‎‏‎‏‏‎‏‏‏‏‏‏‏‏‏‏‎‎‏‎‎‏‏‎‎‏‏‏‎‎‏‏‎‏‏‎‏‏‏‏‎‏‏‏‎‎‎‎‏‏‎‎‏‎‎‏‏‎‏‏‎‎‏‏‎‏‎‏‎‏‎‎‏‏‎‎‏‎‎Bck‎‏‎‎‏‎","browser":"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‎‎‎‏‎‏‏‎‏‏‏‏‏‏‏‏‏‏‎‏‎‎‎‏‎‏‎‎‎‎‏‏‏‎‏‎‏‎‏‏‏‏‏‎‏‏‏‎‎‎‏‏‏‎‎‏‏‏‎‎‎‎‏‎‏‎‏‎‏‏‏‏‎‎‎‏‏‏‏‏‎Browser‎‏‎‎‏‎","forward":"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‎‎‎‏‎‏‏‎‏‏‏‏‏‏‏‎‏‏‏‏‎‏‎‎‏‎‎‏‏‎‎‎‏‏‎‎‏‎‎‏‏‎‎‏‏‏‎‎‎‏‏‎‏‏‏‏‎‎‎‎‏‏‎‎‎‏‏‎‏‎‏‏‎‎‎‏‏‎‎Fwd‎‏‎‎‏‎","refresh":"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‎‎‎‏‎‏‏‎‏‏‏‏‏‏‏‏‏‏‎‏‎‏‎‏‎‎‎‎‎‎‏‏‎‎‏‎‎‏‏‏‎‏‏‎‏‎‎‎‎‎‎‏‎‏‏‏‎‎‏‎‎‎‏‏‎‏‎‏‎‏‏‏‎‏‎‏‎‎‎‏‎Rfrsh‎‏‎‎‏‎","search":"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‎‎‎‏‎‏‏‎‏‏‏‏‏‏‏‏‎‏‏‏‏‏‏‏‎‎‏‎‎‏‎‏‏‏‏‎‎‎‎‎‏‎‏‎‎‎‏‏‏‏‎‏‏‎‎‏‏‏‏‏‎‏‏‎‎‏‎‏‏‏‏‏‎‏‎‎‎‎‏‎Search‎‏‎‎‏‎"}''';
-}
diff --git a/bin/simple_browser_internationalization/lib/localization/messages_nl.dart b/bin/simple_browser_internationalization/lib/localization/messages_nl.dart
deleted file mode 100755
index 263f49b..0000000
--- a/bin/simple_browser_internationalization/lib/localization/messages_nl.dart
+++ /dev/null
@@ -1,33 +0,0 @@
-// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
-// This is a library that provides messages for a nl locale. All the
-// messages from the main program should be duplicated here with the same
-// function name.
-
-// Ignore issues from commonly used lints in this file.
-// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new
-// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
-// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
-// ignore_for_file:unused_import, file_names
-
-import 'package:intl/intl.dart';
-import 'package:intl/message_lookup_by_library.dart';
-import 'dart:convert';
-import 'messages_all.dart' show evaluateJsonTemplate;
-
-final messages = new MessageLookup();
-
-typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
-
-class MessageLookup extends MessageLookupByLibrary {
-  String get localeName => 'nl';
-
-  String? evaluateMessage(translation, List<dynamic> args) {
-    return evaluateJsonTemplate(translation, args);
-  }
-
-  var _messages;
-  get messages => _messages ??=
-      const JsonDecoder().convert(messageText) as Map<String, dynamic>;
-  static final messageText = r'''
-{}''';
-}
diff --git a/bin/simple_browser_internationalization/lib/localization/messages_sr-Latn.dart b/bin/simple_browser_internationalization/lib/localization/messages_sr-Latn.dart
deleted file mode 100755
index 3c6c699..0000000
--- a/bin/simple_browser_internationalization/lib/localization/messages_sr-Latn.dart
+++ /dev/null
@@ -1,33 +0,0 @@
-// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
-// This is a library that provides messages for a sr_Latn locale. All the
-// messages from the main program should be duplicated here with the same
-// function name.
-
-// Ignore issues from commonly used lints in this file.
-// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new
-// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
-// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
-// ignore_for_file:unused_import, file_names
-
-import 'package:intl/intl.dart';
-import 'package:intl/message_lookup_by_library.dart';
-import 'dart:convert';
-import 'messages_all.dart' show evaluateJsonTemplate;
-
-final messages = new MessageLookup();
-
-typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
-
-class MessageLookup extends MessageLookupByLibrary {
-  String get localeName => 'sr_Latn';
-
-  String? evaluateMessage(translation, List<dynamic> args) {
-    return evaluateJsonTemplate(translation, args);
-  }
-
-  var _messages;
-  get messages => _messages ??=
-      const JsonDecoder().convert(messageText) as Map<String, dynamic>;
-  static final messageText = r'''
-{"back":"Nzd","browser":"Pregledač","forward":"Npr","refresh":"Osvž","search":"Pretražite"}''';
-}
diff --git a/bin/simple_browser_internationalization/lib/localization/messages_sr.dart b/bin/simple_browser_internationalization/lib/localization/messages_sr.dart
deleted file mode 100755
index b4fdf94..0000000
--- a/bin/simple_browser_internationalization/lib/localization/messages_sr.dart
+++ /dev/null
@@ -1,33 +0,0 @@
-// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
-// This is a library that provides messages for a sr locale. All the
-// messages from the main program should be duplicated here with the same
-// function name.
-
-// Ignore issues from commonly used lints in this file.
-// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new
-// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
-// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
-// ignore_for_file:unused_import, file_names
-
-import 'package:intl/intl.dart';
-import 'package:intl/message_lookup_by_library.dart';
-import 'dart:convert';
-import 'messages_all.dart' show evaluateJsonTemplate;
-
-final messages = new MessageLookup();
-
-typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
-
-class MessageLookup extends MessageLookupByLibrary {
-  String get localeName => 'sr';
-
-  String? evaluateMessage(translation, List<dynamic> args) {
-    return evaluateJsonTemplate(translation, args);
-  }
-
-  var _messages;
-  get messages => _messages ??=
-      const JsonDecoder().convert(messageText) as Map<String, dynamic>;
-  static final messageText = r'''
-{"back":"Нзд","browser":"Прегледач","forward":"Нпр","refresh":"Освж","search":"Претражите"}''';
-}
diff --git a/bin/simple_browser_internationalization/lib/localizations_delegate.dart b/bin/simple_browser_internationalization/lib/localizations_delegate.dart
deleted file mode 100644
index 2f3ef5c..0000000
--- a/bin/simple_browser_internationalization/lib/localizations_delegate.dart
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// Should the localizations delegate be generated instead?
-
-import 'dart:async';
-import 'dart:ui' show Locale;
-
-import 'package:flutter/material.dart';
-import 'package:fuchsia_logger/logger.dart';
-import 'package:intl/intl.dart';
-
-import 'localization/messages_all.dart' as messages_all;
-import 'supported_locales.dart' as supported_locales;
-
-LocalizationsDelegate<void> delegate() => _LocalizationsDelegate();
-
-class _LocalizationsDelegate extends LocalizationsDelegate<void> {
-  static Future<void> loadLocale(Locale locale) async {
-    final String name =
-        (locale.countryCode == null || locale.countryCode?.isEmpty == true)
-            ? locale.languageCode
-            : locale.toString();
-    final String localeName = Intl.canonicalizedLocale(name);
-    await messages_all.initializeMessages(localeName);
-    log.info(
-        '_LocalizationsDelegate: current default locale: ${Intl.defaultLocale}');
-  }
-
-  const _LocalizationsDelegate();
-
-  @override
-  Future<void> load(Locale locale) => loadLocale(locale);
-
-  // For the time being, never reload.
-  @override
-  bool shouldReload(_LocalizationsDelegate __) => false;
-
-  @override
-  bool isSupported(Locale locale) {
-    bool supported = supported_locales.locales.contains(locale);
-    log.finer(
-        '_LocalizationsDelegate: locale: ${locale.toString()}; isSupported: ${supported.toString()}');
-    return supported;
-  }
-}
diff --git a/bin/simple_browser_internationalization/lib/strings.dart b/bin/simple_browser_internationalization/lib/strings.dart
deleted file mode 100644
index 6e206ad..0000000
--- a/bin/simple_browser_internationalization/lib/strings.dart
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// This file is an L10N developer experience study fragment.
-// Let's see how it works when you offload all strings to a file.
-
-// The Intl.message texts have been offloaded to a separate file so as to
-// minimize the amount of code needed to be exported for localization.
-// While the individual static String get messages have been left in their original form,
-// the case-ness of the text is part of the presentation so should probably be
-// handled in the view code.
-
-import 'package:intl/intl.dart';
-
-/// Provides access to localized strings used in Ermine code.
-class Strings {
-  static final Strings instance = Strings._internal();
-  factory Strings() => instance;
-  Strings._internal();
-
-  static String get back => Intl.message(
-        'Bck',
-        name: 'back',
-        desc: 'A very short label meaning "Go back (to previous web page)"',
-      );
-  static String get forward => Intl.message(
-        'Fwd',
-        name: 'forward',
-        desc: 'A very short label meaning "Go forward (to the next web page)"',
-      );
-  static String get refresh => Intl.message(
-        'Rfrsh',
-        name: 'refresh',
-        desc: 'A very short label meaning "Refresh the web page"',
-      );
-  static String get search => Intl.message(
-        'Search',
-        name: 'search',
-        desc:
-            'A regular length string label appearing in the browser search bar',
-      );
-  static String get browser => Intl.message(
-        'Browser',
-        name: 'browser',
-        desc: 'As in: web browser',
-      );
-  static String get newtab => Intl.message(
-        'New Tab',
-        name: 'newtab',
-        desc: 'A default title for a newly created empty tab.',
-      );
-}
diff --git a/bin/simple_browser_internationalization/lib/supported_locales.dart b/bin/simple_browser_internationalization/lib/supported_locales.dart
deleted file mode 100644
index 84d978f..0000000
--- a/bin/simple_browser_internationalization/lib/supported_locales.dart
+++ /dev/null
@@ -1,10 +0,0 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'dart:ui' show Locale;
-
-List<Locale> locales = [
-  const Locale.fromSubtags(languageCode: 'sr'),
-  const Locale.fromSubtags(languageCode: 'nl'),
-];
diff --git a/bin/simple_browser_internationalization/pubspec.yaml b/bin/simple_browser_internationalization/pubspec.yaml
deleted file mode 100644
index 4c61966..0000000
--- a/bin/simple_browser_internationalization/pubspec.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-# Copyright 2019 The Fuchsia Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-name: internationalization
-description: The internationalization library for Ermine.
-
-flutter:
-
-dependencies:
-  # Needed for the ARB extraction scripts until this is fully automated.
-  intl_translation: ^0.17.2
-
diff --git a/bin/simple_browser_internationalization/resources/intl_messages.arb b/bin/simple_browser_internationalization/resources/intl_messages.arb
deleted file mode 100644
index 9556f92..0000000
--- a/bin/simple_browser_internationalization/resources/intl_messages.arb
+++ /dev/null
@@ -1,33 +0,0 @@
-{
-  "@@last_modified": "2019-11-11T17:42:01.328995",
-  "back": "Bck",
-  "@back": {
-    "description": "A very short label meaning \"Go back (to previous web page)\"",
-    "type": "text",
-    "placeholders": {}
-  },
-  "forward": "Fwd",
-  "@forward": {
-    "description": "A very short label meaning \"Go forward (to the next web page)\"",
-    "type": "text",
-    "placeholders": {}
-  },
-  "refresh": "Rfrsh",
-  "@refresh": {
-    "description": "A very short label meaning \"Refresh the web page\"",
-    "type": "text",
-    "placeholders": {}
-  },
-  "search": "Search",
-  "@search": {
-    "description": "A regular length string label appearing in the browser search bar",
-    "type": "text",
-    "placeholders": {}
-  },
-  "browser": "Browser",
-  "@browser": {
-    "description": "As in: web browser",
-    "type": "text",
-    "placeholders": {}
-  }
-}
diff --git a/bin/simple_browser_internationalization/resources/intl_nl.arb b/bin/simple_browser_internationalization/resources/intl_nl.arb
deleted file mode 100644
index 9728252..0000000
--- a/bin/simple_browser_internationalization/resources/intl_nl.arb
+++ /dev/null
@@ -1,33 +0,0 @@
-{
-  "@@last_modified": "2019-11-06T17:31:05.454814",
-  "back": "Trg",
-  "@back": {
-    "description": "A very short label meaning \"Go back (to previous web page)\"",
-    "type": "text",
-    "placeholders": {}
-  },
-  "forward": "Vor",
-  "@forward": {
-    "description": "A very short label meaning \"Go forward (to the next web page)\"",
-    "type": "text",
-    "placeholders": {}
-  },
-  "refresh": "Vrvrs",
-  "@refresh": {
-    "description": "A very short label meaning \"Refresh the web page\"",
-    "type": "text",
-    "placeholders": {}
-  },
-  "search": "Zoek",
-  "@search": {
-    "description": "A regular length string label appearing in the browser search bar",
-    "type": "text",
-    "placeholders": {}
-  },
-  "browser": "Browser",
-  "@browser": {
-    "description": "As in: web browser",
-    "type": "text",
-    "placeholders": {}
-  }
-}
diff --git a/bin/simple_browser_internationalization/resources/intl_sr.arb b/bin/simple_browser_internationalization/resources/intl_sr.arb
deleted file mode 100644
index 7aca5cd..0000000
--- a/bin/simple_browser_internationalization/resources/intl_sr.arb
+++ /dev/null
@@ -1,33 +0,0 @@
-{
-  "@@last_modified": "2019-11-06T17:31:05.454814",
-  "back": "Наз",
-  "@back": {
-    "description": "A very short label meaning \"Go back (to previous web page)\"",
-    "type": "text",
-    "placeholders": {}
-  },
-  "forward": "Нпр",
-  "@forward": {
-    "description": "A very short label meaning \"Go forward (to the next web page)\"",
-    "type": "text",
-    "placeholders": {}
-  },
-  "refresh": "Освж",
-  "@refresh": {
-    "description": "A very short label meaning \"Refresh the web page\"",
-    "type": "text",
-    "placeholders": {}
-  },
-  "search": "Претрага",
-  "@search": {
-    "description": "A regular length string label appearing in the browser search bar",
-    "type": "text",
-    "placeholders": {}
-  },
-  "browser": "Прегледач",
-  "@browser": {
-    "description": "As in: web browser",
-    "type": "text",
-    "placeholders": {}
-  }
-}
diff --git a/bin/simple_browser_internationalization/scripts/run_extract_to_arb.sh b/bin/simple_browser_internationalization/scripts/run_extract_to_arb.sh
deleted file mode 100755
index d847f84..0000000
--- a/bin/simple_browser_internationalization/scripts/run_extract_to_arb.sh
+++ /dev/null
@@ -1,16 +0,0 @@
-#!/bin/bash
-# Copyright 2019 The Fuchsia Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-set -x
-
-$FUCHSIA_DIR/third_party/dart-pkg/git/flutter/bin/flutter \
-  packages pub get intl_translation:extract_to_arb
-
-$FUCHSIA_DIR/third_party/dart-pkg/git/flutter/bin/flutter \
-  packages pub run intl_translation:extract_to_arb \
-  --output-dir=resources lib/*strings.dart
-
-rm .packages pubspec.lock
-rm -fr .dart_tool
diff --git a/bin/simple_browser_internationalization/scripts/run_generate_from_arb.sh b/bin/simple_browser_internationalization/scripts/run_generate_from_arb.sh
deleted file mode 100755
index 93f7b81..0000000
--- a/bin/simple_browser_internationalization/scripts/run_generate_from_arb.sh
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-# Copyright 2019 The Fuchsia Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-set -x
-
-$FUCHSIA_DIR/third_party/dart-pkg/git/flutter/bin/flutter \
-  packages pub get intl_translation:generate_from_arb
-
-$FUCHSIA_DIR/third_party/dart-pkg/git/flutter/bin/flutter \
-  packages pub run intl_translation:generate_from_arb \
-  --output-dir=lib/localization \
-  lib/*strings.dart \
-  resources/*.arb
-
-rm .packages pubspec.lock
-rm -fr .dart_tool
diff --git a/examples/hello_experiences/BUILD.gn b/examples/hello_experiences/BUILD.gn
index ee4f4c1..c869f36 100644
--- a/examples/hello_experiences/BUILD.gn
+++ b/examples/hello_experiences/BUILD.gn
@@ -18,7 +18,7 @@
 
 flutter_component("component") {
   component_name = "hello-experiences"
-  manifest = "meta/hello-experiences.cmx"
+  manifest = "meta/hello-experiences.cml"
   deps = [ ":lib" ]
 }
 
diff --git a/examples/hello_experiences/meta/hello-experiences.cml b/examples/hello_experiences/meta/hello-experiences.cml
new file mode 100644
index 0000000..3802e53
--- /dev/null
+++ b/examples/hello_experiences/meta/hello-experiences.cml
@@ -0,0 +1,21 @@
+{
+    include: [
+        // Enable system logging.
+        "syslog/client.shard.cml",
+    ],
+    program: {
+        data: "data/hello-experiences"
+    },
+    capabilities: [
+        {
+            protocol: [ "fuchsia.ui.app.ViewProvider" ],
+        },
+    ],
+    expose: [
+        {
+            protocol: [ "fuchsia.ui.app.ViewProvider" ],
+            from: "self",
+            to: "parent"
+        },
+    ],
+}
diff --git a/examples/hello_experiences/meta/hello-experiences.cmx b/examples/hello_experiences/meta/hello-experiences.cmx
deleted file mode 100644
index 21df53e..0000000
--- a/examples/hello_experiences/meta/hello-experiences.cmx
+++ /dev/null
@@ -1,23 +0,0 @@
-{
-    "program": {
-        "data": "data/hello-experiences"
-    },
-    "sandbox": {
-        "services": [
-            "fuchsia.cobalt.LoggerFactory",
-            "fuchsia.fonts.Provider",
-            "fuchsia.logger.LogSink",
-            "fuchsia.modular.ComponentContext",
-            "fuchsia.modular.ModuleContext",
-            "fuchsia.netstack.Netstack",
-            "fuchsia.posix.socket.Provider",
-            "fuchsia.process.Launcher",
-            "fuchsia.sys.Environment",
-            "fuchsia.sys.Launcher",
-            "fuchsia.ui.input.ImeService",
-            "fuchsia.ui.input.ImeVisibilityService",
-            "fuchsia.ui.policy.Presenter",
-            "fuchsia.ui.scenic.Scenic"
-        ]
-    }
-}
diff --git a/examples/localized_flutter/localized_flutter_app/lib/localized_mod_localizations_delegate.dart b/examples/localized_flutter/localized_flutter_app/lib/localized_mod_localizations_delegate.dart
index 10ed432..8bf3943 100644
--- a/examples/localized_flutter/localized_flutter_app/lib/localized_mod_localizations_delegate.dart
+++ b/examples/localized_flutter/localized_flutter_app/lib/localized_mod_localizations_delegate.dart
@@ -48,6 +48,6 @@
   // Delegate containing all app-level messages
   LocalizedModLocalizationsDelegate(),
   // Flutter-provided delegates for Flutter UI messages
-  GlobalMaterialLocalizations.delegate,
+  ...GlobalMaterialLocalizations.delegates,
   GlobalWidgetsLocalizations.delegate,
 ];
diff --git a/lib/BUILD.gn b/lib/BUILD.gn
index 0657544..a876772 100644
--- a/lib/BUILD.gn
+++ b/lib/BUILD.gn
@@ -6,7 +6,6 @@
   deps = [
     "ermine_localhost:ermine_localhost",
     "ermine_ui:ermine_ui",
-    "quickui:quickui",
   ]
 }
 
@@ -18,7 +17,6 @@
     _flutter_tester_tests += [
       "ermine_localhost:ermine_localhost_unittests($host_toolchain)",
       "ermine_ui:ermine_ui_unittests($host_toolchain)",
-      "quickui:quickui_unittests($host_toolchain)",
     ]
   }
 
diff --git a/lib/quickui/BUILD.gn b/lib/quickui/BUILD.gn
deleted file mode 100644
index 22dba1a..0000000
--- a/lib/quickui/BUILD.gn
+++ /dev/null
@@ -1,33 +0,0 @@
-# Copyright 2019 The Fuchsia Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import("//build/dart/dart_library.gni")
-import("//build/dart/test.gni")
-
-dart_library("quickui") {
-  package_name = "quickui"
-  null_safe = true
-
-  sources = [
-    "quickui.dart",
-    "uistream.dart",
-  ]
-
-  deps = [ "//sdk/fidl/fuchsia.ui.remotewidgets" ]
-}
-
-dart_test("quickui_unittests") {
-  null_safe = true
-  sources = [
-    "quickui_test.dart",
-    "uistream_test.dart",
-  ]
-
-  deps = [
-    ":quickui",
-    "//sdk/fidl/fuchsia.ui.remotewidgets",
-    "//third_party/dart-pkg/pub/mockito",
-    "//third_party/dart-pkg/pub/test",
-  ]
-}
diff --git a/lib/quickui/README.md b/lib/quickui/README.md
deleted file mode 100644
index cddc438..0000000
--- a/lib/quickui/README.md
+++ /dev/null
@@ -1 +0,0 @@
-A library to build user interfaces for quick settings and interruptions.
\ No newline at end of file
diff --git a/lib/quickui/lib/quickui.dart b/lib/quickui/lib/quickui.dart
deleted file mode 100644
index adf8d8c..0000000
--- a/lib/quickui/lib/quickui.dart
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'dart:async';
-import 'package:fidl_fuchsia_ui_remotewidgets/fidl_async.dart';
-
-/// Defines an interface for building UI based on [QuickUi] protocol.
-///
-/// This class should be extended and the [update] method should be provided.
-/// The [Spec] for the UI can be returned by setting the [spec] accessor. This
-/// can be called any time to update the UI. If the user of this class provides
-/// a [Value] at the time of requesting the UI, it is passed along in the
-/// [update] method. If this result in a change of the UI, it can be returned
-/// by calling the setter [spec].
-abstract class UiSpec extends QuickUi {
-  // The [Completer] that holds the future for an outstanding [getSpec] request.
-  Completer<Spec> _completer = Completer<Spec>();
-
-  // Constructor.
-  UiSpec([Spec? spec]) {
-    if (spec != null) {
-      _completer.complete(spec);
-    }
-  }
-
-  /// Defines a 'null' spec, used to signal the QuickUI client to hide this
-  /// service's UI.
-  static final Spec nullSpec = Spec();
-
-  /// Completes any outstanding Get for [Spec].
-  set spec(Spec value) {
-    if (_completer.isCompleted) {
-      _completer = Completer<Spec>();
-    }
-    _completer.complete(value);
-  }
-
-  /// Overridden by derived classes.
-  void update(Value value);
-
-  /// Overridden by derived classes.
-  void dispose();
-
-  @override
-  Future<Spec> getSpec([Value? value]) async {
-    if (value != null) {
-      update(value);
-    }
-
-    final future = _completer.future;
-    if (_completer.isCompleted) {
-      _completer = Completer<Spec>();
-    }
-    return future;
-  }
-}
diff --git a/lib/quickui/lib/uistream.dart b/lib/quickui/lib/uistream.dart
deleted file mode 100644
index 064eb36..0000000
--- a/lib/quickui/lib/uistream.dart
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'dart:async';
-import 'package:fidl_fuchsia_ui_remotewidgets/fidl_async.dart';
-
-/// Defines a class that provides a [Stream] of UI [Spec].
-///
-/// The [stream] accessor provides the stream of [Spec] objects. The stream is
-/// started by calling [listen]. Call [dispose] to stop listening.
-/// Method [update] allows requesting a UI [Spec] by sending a [Value] as part
-/// of the request to the underlying [QuickUi] proxy.
-class UiStream {
-  final QuickUi _ui;
-  Spec? _spec;
-  StreamSubscription<Spec>? _subscription;
-  final _controller = StreamController<Spec>.broadcast();
-
-  /// Constructor.
-  UiStream(QuickUi ui) : _ui = ui;
-
-  /// Returns the [Stream] over [Spec] objects.
-  Stream<Spec> get stream => _controller.stream.asBroadcastStream();
-
-  /// Returns the last [Spec] returned from QuickUi server.
-  Spec? get spec => _spec;
-
-  /// Defines a 'null' spec, used by the service to signal the client to hide
-  /// its UI.
-  static final Spec nullSpec = Spec(title: null, groups: null);
-
-  /// Start listening to the [QuickUi] server.
-  void listen() {
-    update();
-  }
-
-  /// Send a [Value] to QuickUi server to request a new [Spec].
-  void update([Value? value]) {
-    _subscription?.cancel();
-    _subscription = _ui.getSpec(value).asStream().listen((spec) {
-      // Cache the spec until the next returned from the server.
-      _spec = spec;
-      _controller.add(spec);
-      listen();
-    });
-  }
-
-  /// Stop listening to the [QuickUi] server.
-  void dispose() {
-    _subscription?.cancel();
-  }
-}
diff --git a/lib/quickui/test/quickui_test.dart b/lib/quickui/test/quickui_test.dart
deleted file mode 100644
index fbb8dee..0000000
--- a/lib/quickui/test/quickui_test.dart
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'package:fidl_fuchsia_ui_remotewidgets/fidl_async.dart';
-import 'package:quickui/quickui.dart';
-import 'package:test/test.dart';
-
-void main() {
-  test('Create UiSpec', () async {
-    final ui = TestUi();
-    final spec = await ui.getSpec(null);
-
-    final group = spec.groups?.first;
-    expect(group?.title, 'Foo');
-    expect(group?.values?.length, 0);
-  });
-
-  test('Update UiSpec', () async {
-    final ui = TestUi();
-    Spec spec = await ui.getSpec(null);
-
-    Group? group = spec.groups?.first;
-    expect(group?.title, 'Foo');
-    expect(group?.values?.length, 0);
-
-    spec = await ui.getSpec(Value.withNumber(NumberValue(
-      value: Number.withIntValue(5),
-      action: 1,
-    )));
-
-    group = spec.groups?.first;
-    expect(group?.title, 'Bar');
-    expect(group?.values?.length, 1);
-    expect(group?.values?.first.$tag, ValueTag.number);
-    expect(group?.values?.first.number?.action, 1);
-    expect(group?.values?.first.number?.value.intValue, 5);
-  });
-}
-
-class TestUi extends UiSpec {
-  TestUi() : super(_build());
-
-  @override
-  void update(Value value) {
-    spec = Spec(
-      title: '',
-      groups: <Group>[
-        Group(title: 'Bar', values: [value]),
-      ],
-    );
-  }
-
-  @override
-  void dispose() {}
-
-  //ignore: prefer_constructors_over_static_methods
-  static Spec _build() {
-    return Spec(title: '', groups: <Group>[Group(title: 'Foo', values: [])]);
-  }
-}
diff --git a/lib/quickui/test/uistream_test.dart b/lib/quickui/test/uistream_test.dart
deleted file mode 100644
index 920ef2c..0000000
--- a/lib/quickui/test/uistream_test.dart
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'package:fidl_fuchsia_ui_remotewidgets/fidl_async.dart';
-import 'package:quickui/quickui.dart';
-import 'package:quickui/uistream.dart';
-import 'package:test/test.dart';
-
-void main() {
-  test('Create UiStream', () async {
-    final ui = TestUi();
-    final uiStream = UiStream(ui)..listen();
-    final spec = await uiStream.stream.first;
-
-    final group = spec.groups?.first;
-    expect(group?.title, 'Foo');
-    expect(group?.values?.length, 0);
-  });
-
-  test('Update UiStream', () async {
-    final ui = TestUi();
-    final uiStream = UiStream(ui)..listen();
-    final stream = uiStream.stream;
-    Spec spec = await stream.first;
-
-    Group? group = spec.groups?.first;
-    expect(group?.title, 'Foo');
-    expect(group?.values?.length, 0);
-
-    ui.update(Value.withNumber(NumberValue(
-      value: Number.withIntValue(5),
-      action: 1,
-    )));
-
-    spec = await stream.skip(1).first;
-
-    group = spec.groups?.first;
-    expect(group?.title, 'Bar');
-    expect(group?.values?.length, 1);
-    expect(group?.values?.first.$tag, ValueTag.number);
-    expect(group?.values?.first.number?.action, 1);
-    expect(group?.values?.first.number?.value.intValue, 5);
-  });
-}
-
-class TestUi extends UiSpec {
-  TestUi() : super(_build());
-
-  @override
-  void update(Value value) {
-    spec = Spec(
-      title: '',
-      groups: <Group>[
-        Group(title: 'Bar', values: [value]),
-      ],
-    );
-  }
-
-  @override
-  void dispose() {}
-
-  //ignore: prefer_constructors_over_static_methods
-  static Spec _build() {
-    return Spec(title: '', groups: <Group>[Group(title: 'Foo', values: [])]);
-  }
-}
diff --git a/session_shells/BUILD.gn b/session_shells/BUILD.gn
index 9f318ee..d84d965 100644
--- a/session_shells/BUILD.gn
+++ b/session_shells/BUILD.gn
@@ -14,13 +14,10 @@
   _flutter_tester_tests = []
   if (host_os != "mac") {
     _flutter_tester_tests += [
-      "ermine/settings:ermine_settings_unittests($host_toolchain)",
       "ermine/shell:ermine_unittests($host_toolchain)",
       "ermine/keyboard_shortcuts:keyboard_shortcuts_unittests($host_toolchain)",
     ]
   }
 
-  deps =
-      [ "//src/experiences/lib/quickui:quickui_unittests($host_toolchain)" ] +
-      _flutter_tester_tests
+  deps = _flutter_tester_tests
 }
diff --git a/session_shells/ermine/BUILD.gn b/session_shells/ermine/BUILD.gn
index c13223b..818a2f2 100644
--- a/session_shells/ermine/BUILD.gn
+++ b/session_shells/ermine/BUILD.gn
@@ -7,8 +7,7 @@
 group("ermine") {
   public_deps = [
     ":ermine-pkg",
-    "session:workstation_routing",
-    "session:workstation_session",
+    "session",
   ]
 }
 
@@ -18,6 +17,7 @@
 
   deps = [
     "login:component",
+    "memfs:component",
     "shell:component",
   ]
 }
diff --git a/session_shells/ermine/internationalization/lib/strings.dart b/session_shells/ermine/internationalization/lib/strings.dart
index 8fb0b83..dbf920d 100644
--- a/session_shells/ermine/internationalization/lib/strings.dart
+++ b/session_shells/ermine/internationalization/lib/strings.dart
@@ -472,6 +472,18 @@
         desc: 'The error text when password does not meet requirements',
       );
 
+  static String get accountPasswordFailedAuthentication => Intl.message(
+        'The password you entered was incorrect.',
+        name: 'accountPasswordFailedAuthentication',
+        desc: 'The error text when password does fails authentication',
+      );
+
+  static String get accountPartitionNotFound => Intl.message(
+        'Account partition not found. Please re-pave the device.',
+        name: 'accountPartitionNotFound',
+        desc: 'The error text when account partition is not found',
+      );
+
   static String get accountPasswordMismatch => Intl.message(
         'The passwords do not match.',
         name: 'accountPasswordMismatch',
@@ -967,6 +979,11 @@
         name: 'increase volume',
         desc: 'Keyboard shortcut description to increase sound volume.',
       );
+  static String get logoutKeyboardShortcut => Intl.message(
+        'Logout',
+        name: 'logout',
+        desc: 'Keyboard shortcut description to logout.',
+      );
   static String get update => Intl.message(
         'Update',
         name: 'update',
@@ -977,6 +994,26 @@
         name: 'continue',
         desc: 'The label for "Continue" text field.',
       );
+  static String get confirmLogoutAlertTitle => Intl.message(
+        'Are you sure you want to log out?',
+        name: 'confirmLogoutAlertTitle',
+        desc: 'The alert dialog title to confirm logout.',
+      );
+  static String get confirmToSaveWorkAlertBody => Intl.message(
+        'This will close all running applications and you could lose unsaved work.',
+        name: 'confirmToSaveWorkAlertBody',
+        desc: 'The alert dialog body to continue logout/restart/shutdown.',
+      );
+  static String get confirmRestartAlertTitle => Intl.message(
+        'Are you sure you want restart?',
+        name: 'confirmRestartAlertTitle',
+        desc: 'The alert dialog title to confirm restart.',
+      );
+  static String get confirmShutdownAlertTitle => Intl.message(
+        'Are you sure you want shutdown?',
+        name: 'confirmShutdownAlertTitle',
+        desc: 'The alert dialog title to confirm shutdown.',
+      );
   static String get channelUpdateAlertTitle => Intl.message(
         'System will reboot',
         name: 'system will reboot',
@@ -1084,16 +1121,46 @@
       );
 
   static String get factoryDataReset => Intl.message(
-        'Factory Data Reset',
+        'Erase all user data and settings and reset password',
         name: 'factoryDataReset',
         desc: 'The label of button to factory data reset a device.',
       );
+
+  static String get factoryDataResetPrompt => Intl.message(
+        'Are you sure to erase all user data and settings and reset the password?',
+        name: 'factoryDataReset',
+        desc: 'The label of button to factory data reset a device.',
+      );
+
+  static String get eraseAndReset => Intl.message(
+        'Erase & Reset',
+        name: 'eraseAndReset',
+        desc: 'The label of button confirm factory data reset.',
+      );
+
   static String get forget => Intl.message(
         'Forget',
         name: 'forget',
         desc: 'The label for "Forget" text field.',
       );
 
+  static String connectToNetwork(String network) => Intl.message(
+        'Connect to $network',
+        name: 'Connect to network',
+        desc: 'The prompt text to connected to selected network.',
+        args: [network],
+      );
+  static String get connected => Intl.message(
+        'Connected',
+        name: 'connected',
+        desc: 'The label for "Connected" text field.',
+      );
+  static String get incorrectPassword => Intl.message(
+        'Incorrect Password',
+        name: 'incorrect password',
+        desc: 'The label for "Incorrect Password" text field.',
+      );
+
   /// Lookup message given it's name.
   static String? lookup(String name) {
     final _messages = <String, String>{
diff --git a/session_shells/ermine/login/BUILD.gn b/session_shells/ermine/login/BUILD.gn
index 9ad4e07..ec75318 100644
--- a/session_shells/ermine/login/BUILD.gn
+++ b/session_shells/ermine/login/BUILD.gn
@@ -6,9 +6,11 @@
 import("//build/dart/dart_library.gni")
 import("//build/fidl/fidl.gni")
 import("//build/flutter/flutter_component.gni")
+import("//build/testing/flutter_driver.gni")
 
 declare_args() {
-  # Whether or not to launch OOBE workflow on startup.
+  # TODO(http://fxb/85576): Whether or not to launch OOBE workflow on startup.
+  # This feature is still WIP but you can turn it on at your own risk.
   start_oobe = false
 }
 
@@ -16,9 +18,13 @@
   package_name = "login"
   null_safe = true
 
-  entrypoints = [ "main.dart" ]
+  entrypoints = [
+    "main.dart",
+    "test_main.dart",
+  ]
 
   services = [
+    "src/services/auth_service.dart",
     "src/services/channel_service.dart",
     "src/services/device_service.dart",
     "src/services/privacy_consent_service.dart",
@@ -48,26 +54,35 @@
 
   deps = [
     "//sdk/dart/fidl",
+    "//sdk/dart/fuchsia_inspect",
     "//sdk/dart/fuchsia_internationalization_flutter",
     "//sdk/dart/fuchsia_logger",
     "//sdk/dart/fuchsia_scenic_flutter",
     "//sdk/dart/fuchsia_services",
+    "//sdk/dart/fuchsia_vfs",
     "//sdk/dart/zircon",
-    "//sdk/fidl/fuchsia.device.manager",
+    "//sdk/fidl/fuchsia.component",
+    "//sdk/fidl/fuchsia.component.decl",
     "//sdk/fidl/fuchsia.element",
     "//sdk/fidl/fuchsia.feedback",
+    "//sdk/fidl/fuchsia.hardware.power.statecontrol",
+    "//sdk/fidl/fuchsia.identity.account",
     "//sdk/fidl/fuchsia.intl",
+    "//sdk/fidl/fuchsia.io",
     "//sdk/fidl/fuchsia.mem",
+    "//sdk/fidl/fuchsia.recovery",
     "//sdk/fidl/fuchsia.settings",
     "//sdk/fidl/fuchsia.ssh",
     "//sdk/fidl/fuchsia.sys",
     "//sdk/fidl/fuchsia.ui.app",
     "//sdk/fidl/fuchsia.ui.focus",
+    "//sdk/fidl/fuchsia.ui.scenic",
     "//sdk/fidl/fuchsia.ui.views",
     "//sdk/fidl/fuchsia.update.channelcontrol",
     "//src/experiences/session_shells/ermine/internationalization",
     "//src/experiences/session_shells/ermine/utils:ermine_utils",
     "//third_party/dart-pkg/git/flutter/packages/flutter",
+    "//third_party/dart-pkg/git/flutter/packages/flutter_driver",
     "//third_party/dart-pkg/git/flutter/packages/flutter_localizations",
     "//third_party/dart-pkg/git/flutter/packages/flutter_test",
     "//third_party/dart-pkg/pub/async",
@@ -78,9 +93,16 @@
 }
 
 flutter_component("component") {
-  main_dart = "main.dart"
+  if (flutter_driver_enabled) {
+    main_dart = "test_main.dart"
+  } else {
+    main_dart = "main.dart"
+  }
+
   component_name = "login"
-  manifest = "meta/login.cmx"
+
+  manifest = "meta/login.cml"
+
   deps = [
     ":images",
     ":lib",
@@ -94,13 +116,6 @@
   }
 }
 
-# fuchsia_package("login") {
-#   deps = [
-#     ":component",
-#     ":images",
-#   ]
-# }
-
 resource("resources") {
   sources = [
     "config/privacy_policy.txt",
@@ -115,7 +130,9 @@
 config_data("default_config") {
   for_pkg = "ermine"
 
-  sources = [ "config/default_config.json" ]
+  sources = [
+    "//src/experiences/session_shells/ermine/login/config/default_config.json",
+  ]
   outputs = [ "startup_config.json" ]
 }
 
@@ -123,7 +140,9 @@
 config_data("oobe_config") {
   for_pkg = "ermine"
 
-  sources = [ "config/oobe_config.json" ]
+  sources = [
+    "//src/experiences/session_shells/ermine/login/config/oobe_config.json",
+  ]
   outputs = [ "startup_config.json" ]
 }
 
diff --git a/session_shells/ermine/login/lib/main.dart b/session_shells/ermine/login/lib/main.dart
index 9636aa6..5d3a7d5 100644
--- a/session_shells/ermine/login/lib/main.dart
+++ b/session_shells/ermine/login/lib/main.dart
@@ -16,7 +16,13 @@
     setupLogger(name: 'login');
     final oobe = OobeState.fromEnv();
     final app = Observer(builder: (_) {
-      return oobe.loginDone ? ErmineApp(oobe) : OobeApp(oobe);
+      return !oobe.ready
+          ? Offstage()
+          : oobe.loginDone
+              ? ErmineApp(oobe)
+              : oobe.launchOobe
+                  ? OobeApp(oobe)
+                  : Offstage();
     });
     runApp(app);
   });
diff --git a/session_shells/ermine/login/lib/src/services/auth_service.dart b/session_shells/ermine/login/lib/src/services/auth_service.dart
new file mode 100644
index 0000000..cb2309d
--- /dev/null
+++ b/session_shells/ermine/login/lib/src/services/auth_service.dart
@@ -0,0 +1,216 @@
+// Copyright 2021 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:fidl/fidl.dart';
+import 'package:ermine_utils/ermine_utils.dart';
+import 'package:fidl_fuchsia_identity_account/fidl_async.dart';
+import 'package:fidl_fuchsia_io/fidl_async.dart';
+import 'package:fidl_fuchsia_recovery/fidl_async.dart';
+import 'package:fuchsia_logger/logger.dart';
+import 'package:fuchsia_services/services.dart';
+import 'package:fuchsia_vfs/vfs.dart';
+import 'package:internationalization/strings.dart';
+import 'package:mobx/mobx.dart';
+import 'package:zircon/zircon.dart';
+
+const kDeprecatedAccountName = 'created_by_user';
+const kSystemPickedAccountName = 'picked_by_system';
+const kUserPickedAccountName = 'picked_by_user';
+const kAccountDataDirectory = 'account_data';
+const kAccountCacheDirectory = 'account_cache';
+const kCacheSubdirectory = 'cache/';
+
+enum AuthMode { automatic, manual }
+
+/// Defines a service that performs authentication tasks like:
+/// - create an account with password
+/// - login to an account with password
+/// - logout from an account
+///
+/// Note:
+/// - It always picks the first account for login and logout.
+/// - Creating an account, when an account already exists, is an undefined
+///   behavior. The client of the service should ensure to not call account
+///   creation in this case.
+class AuthService {
+  late final PseudoDir hostedDirectories;
+
+  final _accountManager = AccountManagerProxy();
+  AccountProxy? _account;
+  final _accountIds = <int>[];
+  final _ready = false.asObservable();
+
+  AuthService() {
+    Incoming.fromSvcPath().connectToService(_accountManager);
+  }
+
+  void dispose() {
+    _accountManager.ctrl.close();
+  }
+
+  /// Load existing accounts from [AccountManager].
+  void loadAccounts(AuthMode currentAuthMode) async {
+    try {
+      final ids = (await _accountManager.getAccountIds()).toList();
+
+      // TODO(http://fxb/85576): Remove once login and OOBE are mandatory.
+      // Remove any accounts created with a deprecated name or from an auth
+      // mode that does not match the current build configuration.
+      final tempIds = <int>[]..addAll(ids);
+      for (var id in tempIds) {
+        final metadata = await _accountManager.getAccountMetadata(id);
+
+        if (metadata.name != null &&
+            _shouldRemoveAccountWithName(metadata.name!, currentAuthMode)) {
+          try {
+            await _accountManager.removeAccount(id, true);
+            ids.remove(id);
+            log.info('Removed account: $id with name: ${metadata.name}');
+            // ignore: avoid_catches_without_on_clauses
+          } catch (e) {
+            // We can only log and continue.
+            log.shout('Failed during deprecated account removal: $e');
+          }
+        }
+      }
+      _accountIds.addAll(ids);
+      runInAction(() => _ready.value = true);
+      if (ids.length > 1) {
+        log.shout(
+            'Multiple (${ids.length}) accounts found, will use the first.');
+      }
+      // ignore: avoid_catches_without_on_clauses
+    } catch (e) {
+      log.shout('Failed during deprecated account removal: $e');
+    }
+  }
+
+  bool _shouldRemoveAccountWithName(String name, AuthMode currentAuthMode) {
+    if (name == kDeprecatedAccountName) {
+      return true;
+    }
+    if (currentAuthMode == AuthMode.automatic) {
+      // Current auth is automatic, remove account with user picked name.
+      return name == kUserPickedAccountName;
+    } else {
+      // Current auth is manual, remove account with system picked name.
+      return name == kSystemPickedAccountName;
+    }
+  }
+
+  /// Calls [FactoryReset] service to factory data reset the device.
+  void factoryReset() {
+    final proxy = FactoryResetProxy();
+    Incoming.fromSvcPath().connectToService(proxy);
+    proxy
+        .reset()
+        .then((status) => log.info('Requested factory reset.'))
+        .catchError((e) => log.shout('Failed to factory reset device: $e'));
+  }
+
+  /// Returns [true] after [_accountManager.getAccountIds()] completes.
+  bool get ready => _ready.value;
+
+  /// Returns [true] if no accounts exists on device.
+  bool get hasAccount {
+    assert(ready, 'Called before list of accounts could be retrieved.');
+    return _accountIds.isNotEmpty;
+  }
+
+  String errorFromException(Object e) {
+    if (e is MethodException) {
+      switch (e.value as Error) {
+        case Error.failedAuthentication:
+          return Strings.accountPasswordFailedAuthentication;
+        case Error.notFound:
+          return Strings.accountPartitionNotFound;
+      }
+    }
+    return e.toString();
+  }
+
+  /// Creates an account with password and sets up the account data directory.
+  Future<void> createAccountWithPassword(String password) async {
+    assert(_account == null, 'An account already exists.');
+    if (_account != null && _account!.ctrl.isBound) {
+      // ignore: unawaited_futures
+      _account!.lock().catchError((_) {});
+      _account!.ctrl.close();
+    }
+
+    final metadata = AccountMetadata(
+        name: password.isEmpty
+            ? kSystemPickedAccountName
+            : kUserPickedAccountName);
+    _account = AccountProxy();
+    await _accountManager.deprecatedProvisionNewAccount(
+      password,
+      metadata,
+      _account!.ctrl.request(),
+    );
+    final ids = await _accountManager.getAccountIds();
+    _accountIds
+      ..clear()
+      ..addAll(ids);
+    log.info('Account creation succeeded.');
+
+    await _publishAccountDirectory(_account!);
+  }
+
+  /// Logs in to the first account with [password] and sets up the account data
+  /// directory.
+  Future<void> loginWithPassword(String password) async {
+    assert(_accountIds.isNotEmpty, 'No account exist to login to.');
+    if (_account != null && _account!.ctrl.isBound) {
+      // ignore: unawaited_futures
+      _account!.lock().catchError((_) {});
+      _account!.ctrl.close();
+    }
+
+    _account = AccountProxy();
+    await _accountManager.deprecatedGetAccount(
+      _accountIds.first,
+      password,
+      _account!.ctrl.request(),
+    );
+    log.info('Login to first account on device succeeded.');
+
+    await _publishAccountDirectory(_account!);
+  }
+
+  /// Logs out of an account by locking it.
+  Future<void> logout() async {
+    assert(_account != null, 'No account exists to logout from.');
+    return _account!.lock();
+  }
+
+  Future<void> _publishAccountDirectory(Account account) async {
+    // Get the data directory for the account.
+    log.info('Getting data directory for account.');
+    final dataDirChannel = ChannelPair();
+    await account.getDataDirectory(InterfaceRequest(dataDirChannel.second));
+
+    // Open or create a subdirectory for the cache storage capability.
+    log.info('Opening cache directory for account.');
+    final dataDir = RemoteDir(dataDirChannel.first!);
+    final cacheDirChannel = ChannelPair();
+    dataDir.open(
+        openRightReadable |
+            openRightWritable |
+            openFlagCreate |
+            openFlagDirectory,
+        0,
+        kCacheSubdirectory,
+        InterfaceRequest(cacheDirChannel.second));
+
+    // Host both directories.
+    hostedDirectories
+      ..removeNode(kAccountDataDirectory)
+      ..addNode(kAccountDataDirectory, dataDir)
+      ..removeNode(kAccountCacheDirectory)
+      ..addNode(kAccountCacheDirectory, RemoteDir(cacheDirChannel.first!));
+
+    log.info('Data and cache directories for account published.');
+  }
+}
diff --git a/session_shells/ermine/login/lib/src/services/device_service.dart b/session_shells/ermine/login/lib/src/services/device_service.dart
index 80ab868..b4ba9a7 100644
--- a/session_shells/ermine/login/lib/src/services/device_service.dart
+++ b/session_shells/ermine/login/lib/src/services/device_service.dart
@@ -2,13 +2,18 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import 'package:fidl_fuchsia_device_manager/fidl_async.dart';
+import 'package:fidl_fuchsia_hardware_power_statecontrol/fidl_async.dart';
+import 'package:fuchsia_inspect/inspect.dart';
 import 'package:fuchsia_services/services.dart';
 
 /// Defines a service to handle device-specific operations like shutdown and
 /// factory data reset.
 class DeviceService {
-  final _deviceManager = AdministratorProxy();
+  /// Callback to service [Inspect] requests from the system.
+  late final void Function(Node) onInspect;
+
+  final _deviceManager = AdminProxy();
+  final _inspect = Inspect();
 
   DeviceService() {
     Incoming.fromSvcPath().connectToService(_deviceManager);
@@ -18,5 +23,11 @@
     _deviceManager.ctrl.close();
   }
 
-  void shutdown() => _deviceManager.suspend(suspendFlagPoweroff);
+  void serve(ComponentContext componentContext) {
+    _inspect
+      ..serve(componentContext.outgoing)
+      ..onDemand('login', onInspect);
+  }
+
+  void shutdown() => _deviceManager.poweroff();
 }
diff --git a/session_shells/ermine/login/lib/src/services/shell_service.dart b/session_shells/ermine/login/lib/src/services/shell_service.dart
index 84e3b9e..51f673e 100644
--- a/session_shells/ermine/login/lib/src/services/shell_service.dart
+++ b/session_shells/ermine/login/lib/src/services/shell_service.dart
@@ -4,76 +4,108 @@
 
 import 'dart:async';
 
-import 'package:fidl_fuchsia_element/fidl_async.dart';
-import 'package:fidl_fuchsia_sys/fidl_async.dart';
+import 'package:ermine_utils/ermine_utils.dart';
+import 'package:fidl_fuchsia_component/fidl_async.dart';
+import 'package:fidl_fuchsia_component_decl/fidl_async.dart';
+import 'package:fidl_fuchsia_io/fidl_async.dart';
 import 'package:fidl_fuchsia_ui_app/fidl_async.dart';
+import 'package:fidl_fuchsia_ui_scenic/fidl_async.dart';
 import 'package:fidl_fuchsia_ui_views/fidl_async.dart' hide FocusState;
+import 'package:fuchsia_logger/logger.dart';
 import 'package:fuchsia_scenic_flutter/fuchsia_view.dart';
 import 'package:fuchsia_services/services.dart';
+import 'package:flutter/material.dart';
+import 'package:mobx/mobx.dart';
 import 'package:zircon/zircon.dart';
 
 /// Defines a service to launch and support Ermine user shell.
 class ShellService {
-  final _graphicalPresenter = GraphicalPresenterProxy();
-  late final FuchsiaViewConnection _fuchsiaViewConnection;
-  bool _focusRequested = false;
   late final StreamSubscription<bool> _focusSubscription;
+  late final VoidCallback onShellReady;
+  late final VoidCallback onShellExit;
+  late final bool _useFlatland;
+  _ErmineViewConnection? _ermine;
 
-  void advertise(Outgoing outgoing) {
-    outgoing.addPublicService((request) {
-      GraphicalPresenterBinding().bind(_graphicalPresenter, request);
-    }, GraphicalPresenter.$serviceName);
+  ShellService() {
+    ScenicProxy scenic = ScenicProxy();
+    Incoming.fromSvcPath().connectToService(scenic);
+    scenic.usesFlatland().then((scenicUsesFlatland) {
+      _useFlatland = scenicUsesFlatland;
+      runInAction(() => {_ready.value = true});
+    });
+    WidgetsFlutterBinding.ensureInitialized();
+    _focusSubscription = FocusState.instance.stream().listen(_onFocusChanged);
   }
 
+  /// Returns [true] after call to [Scenic.usesFlatland] completes.
+  bool get ready => _ready.value;
+  final _ready = false.asObservable();
+
   void dispose() {
     _focusSubscription.cancel();
-    _graphicalPresenter.ctrl.close();
   }
 
   /// Launch Ermine shell and return [FuchsiaViewConnection].
   FuchsiaViewConnection launchErmineShell() {
-    _focusSubscription = FocusState.instance.stream().listen(_onFocusChanged);
-
-    final incoming = Incoming();
-    final componentController = ComponentControllerProxy();
-    final elementManager = ManagerProxy();
-
-    final launcher = LauncherProxy();
-    Incoming.fromSvcPath()
-      ..connectToService(elementManager)
-      ..connectToService(launcher)
-      ..close();
-
-    final binding = ServiceProviderBinding();
-    final provider = ServiceProviderImpl()
-      ..addServiceForName(
-        (request) => ManagerBinding().bind(elementManager, request),
-        Manager.$serviceName,
-      );
-
-    launcher.createComponent(
-      LaunchInfo(
-        url: 'fuchsia-pkg://fuchsia.com/ermine#meta/ermine.cmx',
-        directoryRequest: incoming.request().passChannel(),
-        additionalServices: ServiceList(
-          names: [Manager.$serviceName],
-          provider: binding.wrap(provider),
-        ),
-      ),
-      componentController.ctrl.request(),
+    assert(_ermine == null, 'Instance of ermine shell already exists.');
+    _ermine = _ErmineViewConnection(
+      useFlatland: _useFlatland,
+      onReady: onShellReady,
+      onExit: onShellExit,
     );
-    launcher.ctrl.close();
+    return _ermine!.fuchsiaViewConnection;
+  }
 
-    ViewProviderProxy viewProvider = ViewProviderProxy();
-    incoming
-      ..connectToService(viewProvider)
-      ..connectToService(_graphicalPresenter);
+  void disposeErmineShell() {
+    _ermine = null;
+  }
 
-    // TODO(fxbug.dev/86450): Flip this to use Flatland instead of legacy Scenic
-    // Gfx API.
-    const useFlatland = false;
+  // Transfer focus to Ermine shell whenever login shell receives focus.
+  void _onFocusChanged(bool focused) {
+    if (focused) {
+      _ermine?.setFocus();
+    }
+  }
+}
 
-    // ignore: dead_code
+class _ErmineViewConnection {
+  final bool useFlatland;
+  final VoidCallback onReady;
+  final VoidCallback onExit;
+  late final FuchsiaViewConnection fuchsiaViewConnection;
+  bool _focusRequested = false;
+
+  _ErmineViewConnection({
+    required this.useFlatland,
+    required this.onReady,
+    required this.onExit,
+  }) {
+    // Connect to the Realm.
+    final realm = RealmProxy();
+    Incoming.fromSvcPath().connectToService(realm);
+
+    // Get the ermine shell's exposed /svc directory.
+    final exposedDir = DirectoryProxy();
+    realm.openExposedDir(
+        ChildRef(name: 'ermine_shell'), exposedDir.ctrl.request());
+
+    // Get the ermine shell's view provider.
+    final viewProvider = ViewProviderProxy();
+    Incoming.withDirectory(exposedDir).connectToService(viewProvider);
+    viewProvider.ctrl.whenClosed.then((_) => onExit());
+
+    fuchsiaViewConnection = _launch(viewProvider);
+  }
+
+  void setFocus() {
+    if (_focusRequested) {
+      fuchsiaViewConnection.requestFocus().catchError((e) {
+        log.shout(e);
+      });
+    }
+  }
+
+  FuchsiaViewConnection _launch(ViewProvider viewProvider) {
     if (useFlatland) {
       final viewTokens = ChannelPair();
       assert(viewTokens.status == ZX.OK);
@@ -84,57 +116,42 @@
       final createViewArgs =
           CreateView2Args(viewCreationToken: viewCreationToken);
       viewProvider.createView2(createViewArgs);
-      viewProvider.ctrl.close();
 
-      // TODO(fxbug.dev/86649): We should let the child send us the one they
-      // minted for Flatland. Once that is available, we can call requestFocus()
-      // on onViewStateChanged.
-      return _fuchsiaViewConnection = FuchsiaViewConnection.flatland(
+      return FuchsiaViewConnection.flatland(
         viewportCreationToken,
-        onViewStateChanged: (_, state) {
-          // Wait until ermine shell has rendered before focusing it.
-          if (state == true && !_focusRequested) {
-            _focusRequested = true;
-          }
-        },
+        onViewStateChanged: _onViewStateChanged,
+      );
+    } else {
+      final viewTokens = EventPairPair();
+      assert(viewTokens.status == ZX.OK);
+      final viewHolderToken = ViewHolderToken(value: viewTokens.first!);
+      final viewToken = ViewToken(value: viewTokens.second!);
+
+      final viewRefPair = EventPairPair();
+      final viewRef =
+          ViewRef(reference: viewRefPair.first!.duplicate(ZX.RIGHTS_BASIC));
+      final viewRefControl = ViewRefControl(
+          reference: viewRefPair.second!
+              .duplicate(ZX.DEFAULT_EVENTPAIR_RIGHTS & (~ZX.RIGHT_DUPLICATE)));
+      final viewRefInject =
+          ViewRef(reference: viewRefPair.first!.duplicate(ZX.RIGHTS_BASIC));
+
+      viewProvider.createViewWithViewRef(
+          viewToken.value, viewRefControl, viewRef);
+
+      return FuchsiaViewConnection(
+        viewHolderToken,
+        viewRef: viewRefInject,
+        onViewStateChanged: _onViewStateChanged,
       );
     }
-
-    final viewTokens = EventPairPair();
-    assert(viewTokens.status == ZX.OK);
-    final viewHolderToken = ViewHolderToken(value: viewTokens.first!);
-    final viewToken = ViewToken(value: viewTokens.second!);
-
-    final viewRefPair = EventPairPair();
-    final viewRef =
-        ViewRef(reference: viewRefPair.first!.duplicate(ZX.RIGHTS_BASIC));
-    final viewRefControl = ViewRefControl(
-        reference: viewRefPair.second!
-            .duplicate(ZX.DEFAULT_EVENTPAIR_RIGHTS & (~ZX.RIGHT_DUPLICATE)));
-    final viewRefInject =
-        ViewRef(reference: viewRefPair.first!.duplicate(ZX.RIGHTS_BASIC));
-
-    viewProvider.createViewWithViewRef(
-        viewToken.value, viewRefControl, viewRef);
-    viewProvider.ctrl.close();
-
-    return _fuchsiaViewConnection = FuchsiaViewConnection(
-      viewHolderToken,
-      viewRef: viewRefInject,
-      onViewStateChanged: (_, state) {
-        // Wait until ermine shell has rendered before focusing it.
-        if (state == true && !_focusRequested) {
-          _focusRequested = true;
-          _fuchsiaViewConnection.requestFocus();
-        }
-      },
-    );
   }
 
-  void _onFocusChanged(bool focused) {
-    if (_focusRequested && focused) {
-      // ignore: unawaited_futures
-      _fuchsiaViewConnection.requestFocus();
+  void _onViewStateChanged(FuchsiaViewController _, bool? state) {
+    if (state == true && !_focusRequested) {
+      _focusRequested = true;
+      setFocus();
+      onReady();
     }
   }
 }
diff --git a/session_shells/ermine/login/lib/src/states/oobe_state.dart b/session_shells/ermine/login/lib/src/states/oobe_state.dart
index ebf21ac..af062b0 100644
--- a/session_shells/ermine/login/lib/src/states/oobe_state.dart
+++ b/session_shells/ermine/login/lib/src/states/oobe_state.dart
@@ -5,6 +5,7 @@
 import 'dart:ui';
 
 import 'package:fuchsia_scenic_flutter/fuchsia_view.dart';
+import 'package:login/src/services/auth_service.dart';
 import 'package:login/src/services/channel_service.dart';
 import 'package:login/src/services/device_service.dart';
 import 'package:login/src/services/privacy_consent_service.dart';
@@ -15,7 +16,7 @@
 /// The oobe screens the user navigates through.
 // TODO(fxbug.dev/73407): Skip data sharing screen until privacy policy is
 // finalized.
-enum OobeScreen { channel, /* dataSharing, */ sshKeys, password, done }
+enum OobeScreen { /* channel,  dataSharing, sshKeys,*/ password, done }
 
 /// The screens the user navigates through to add SSH keys.
 enum SshScreen { add, confirm, error, exit }
@@ -39,7 +40,11 @@
   abstract int sshKeyIndex;
   bool get privacyVisible;
   bool get launchOobe;
+  bool get ready;
+  bool get hasAccount;
   bool get loginDone;
+  bool get wait;
+  String get authError;
 
   FuchsiaViewConnection get ermineViewConnection;
   String get privacyPolicy;
@@ -59,9 +64,12 @@
   void skip();
   void finish();
   void shutdown();
+  void factoryReset();
+  void resetAuthError();
 
   factory OobeState.fromEnv() {
     return OobeStateImpl(
+      authService: AuthService(),
       deviceService: DeviceService(),
       shellService: ShellService(),
       channelService: ChannelService(),
diff --git a/session_shells/ermine/login/lib/src/states/oobe_state_impl.dart b/session_shells/ermine/login/lib/src/states/oobe_state_impl.dart
index 6363a57..f8b3be5 100644
--- a/session_shells/ermine/login/lib/src/states/oobe_state_impl.dart
+++ b/session_shells/ermine/login/lib/src/states/oobe_state_impl.dart
@@ -8,24 +8,30 @@
 
 import 'package:ermine_utils/ermine_utils.dart';
 import 'package:flutter/services.dart';
+import 'package:fuchsia_inspect/inspect.dart';
 import 'package:fuchsia_logger/logger.dart';
 import 'package:fuchsia_scenic_flutter/fuchsia_view.dart';
 import 'package:fuchsia_services/services.dart';
+import 'package:fuchsia_vfs/vfs.dart';
 import 'package:internationalization/strings.dart';
-import 'package:mobx/mobx.dart';
+import 'package:login/src/services/auth_service.dart';
 import 'package:login/src/services/channel_service.dart';
 import 'package:login/src/services/device_service.dart';
 import 'package:login/src/services/privacy_consent_service.dart';
 import 'package:login/src/services/shell_service.dart';
 import 'package:login/src/services/ssh_keys_service.dart';
 import 'package:login/src/states/oobe_state.dart';
+import 'package:mobx/mobx.dart';
+
+const kHostedDirectories = 'hosted_directories';
 
 /// Defines an implementation of [OobeState].
 class OobeStateImpl with Disposable implements OobeState {
-  static const kDefaultConfigJson = '/config/data/startup_config.json';
+  static const kDefaultConfigJson = '/config/data/ermine/startup_config.json';
   static const kStartupConfigJson = '/data/startup_config.json';
 
   final ComponentContext componentContext;
+  final AuthService authService;
   final ChannelService channelService;
   final DeviceService deviceService;
   final SshKeysService sshKeysService;
@@ -33,6 +39,7 @@
   final PrivacyConsentService privacyConsentService;
 
   OobeStateImpl({
+    required this.authService,
     required this.deviceService,
     required this.shellService,
     required this.channelService,
@@ -41,6 +48,24 @@
   })  : componentContext = ComponentContext.create(),
         _localeStream = channelService.stream.asObservable() {
     privacyPolicy = privacyConsentService.privacyPolicy;
+    shellService
+      ..onShellReady = _onErmineShellReady
+      ..onShellExit = _onErmineShellExit;
+    deviceService
+      ..onInspect = _onInspect
+      ..serve(componentContext);
+
+    // Create a directory that will host the account_data directory. This is needed
+    // because the root directories are expected to exist at the time of serving.
+    final hostedDirectories = PseudoDir();
+    componentContext.outgoing
+        .rootDir()
+        .addNode(kHostedDirectories, hostedDirectories);
+
+    authService
+      ..hostedDirectories = hostedDirectories
+      ..loadAccounts(launchOobe ? AuthMode.manual : AuthMode.automatic);
+    componentContext.outgoing.serveFromStartupInfo();
 
     channelService.onConnected = (connected) => runInAction(() async {
           if (connected) {
@@ -51,8 +76,6 @@
           }
           _updateChannelsAvailable.value = connected;
         });
-    shellService.advertise(componentContext.outgoing);
-    componentContext.outgoing.serveFromStartupInfo();
 
     // We cannot load MaterialIcons font file from pubspec.yaml. So load it
     // explicitly.
@@ -65,11 +88,22 @@
         }())
         ..load();
     }
+
+    // TODO(http://fxb/85576): Remove once login and OOBE are mandatory.
+    // If we are skipping OOBE, authenticate using empty password.
+    reactions.add(reaction((_) => ready, (ready) {
+      if (!launchOobe) {
+        _performNullLogin().then((_) {
+          runInAction(() => _loginDone.value = true);
+        });
+      }
+    }));
   }
 
   @override
   void dispose() {
     super.dispose();
+    authService.dispose();
     channelService.dispose();
     privacyConsentService.dispose();
     sshKeysService.dispose();
@@ -78,13 +112,8 @@
 
   @override
   bool get launchOobe => _launchOobe.value;
-  set launchOobe(bool value) => runInAction(() => _launchOobe.value = value);
-  final Observable<bool> _launchOobe = Observable<bool>(() {
-    File config = File(kStartupConfigJson);
-    // If startup config does not exist, open the default config.
-    if (!config.existsSync()) {
-      config = File(kDefaultConfigJson);
-    }
+  late final _launchOobe = Observable<bool>(() {
+    File config = File(kDefaultConfigJson);
     // If default config is missing, log error and return defaults.
     if (!config.existsSync()) {
       log.severe('Missing startup and default configs. Skipping OOBE.');
@@ -95,21 +124,34 @@
   }());
 
   @override
+  bool get ready => _ready.value;
+  late final _ready = (() {
+    return shellService.ready && authService.ready;
+  }).asComputed();
+
+  @override
+  bool get hasAccount {
+    // This should be called only after startup services are ready.
+    assert(ready, 'Startup services are not initialized.');
+    return authService.hasAccount;
+  }
+
+  @override
   bool get loginDone => _loginDone.value;
-  final _loginDone = true.asObservable();
+  final _loginDone = false.asObservable();
 
   @override
   Locale? get locale => _localeStream.value;
   final ObservableStream<Locale> _localeStream;
 
-  FuchsiaViewConnection? _ermineViewConnection;
+  final _ermineViewConnection = Observable<FuchsiaViewConnection?>(null);
   @override
   FuchsiaViewConnection get ermineViewConnection =>
-      _ermineViewConnection ??= shellService.launchErmineShell();
+      _ermineViewConnection.value ??= shellService.launchErmineShell();
 
   @override
   OobeScreen get screen => _screen.value;
-  final Observable<OobeScreen> _screen = OobeScreen.channel.asObservable();
+  final Observable<OobeScreen> _screen = OobeScreen.password.asObservable();
 
   @override
   bool get updateChannelsAvailable => _updateChannelsAvailable.value;
@@ -180,6 +222,14 @@
   late final String privacyPolicy;
 
   @override
+  String get authError => _authError.value;
+  final _authError = ''.asObservable();
+
+  @override
+  bool get wait => _wait.value;
+  final _wait = false.asObservable();
+
+  @override
   void setCurrentChannel(String channel) => runInAction(() async {
         await channelService.setCurrentChannel(channel);
         _currentChannel.value = await channelService.currentChannel;
@@ -279,27 +329,120 @@
 
   @override
   void finish() => runInAction(() {
-        // Dismiss OOBE UX.
-        launchOobe = false;
-
         // Mark login step as done.
         _loginDone.value = true;
-
-        // Persistently record OOBE done.
-        File(kStartupConfigJson).writeAsStringSync('{"launch_oobe":false}');
-
-        // Clean up.
-        dispose();
       });
 
   @override
-  // TODO(http://fxb/81598): Implement create password functionality.
-  void setPassword(String password) => nextScreen();
+  void setPassword(String password) async {
+    try {
+      runInAction(() {
+        _authError.value = '';
+        _wait.value = true;
+      });
+      await authService.createAccountWithPassword(password);
+      runInAction(() => _wait.value = false);
+      nextScreen();
+      // ignore: avoid_catches_without_on_clauses
+    } catch (e) {
+      log.shout('Caught exception during account creation: $e');
+      runInAction(() {
+        _wait.value = false;
+        _authError.value = authService.errorFromException(e);
+      });
+    }
+  }
 
   @override
-  // TODO(http://fxb/81598): Implement login functionality.
-  void login(String password) => finish();
+  void resetAuthError() => runInAction(() => _authError.value = '');
+
+  @override
+  void login(String password) async {
+    try {
+      runInAction(() {
+        _authError.value = '';
+        _wait.value = true;
+      });
+      await authService.loginWithPassword(password);
+      runInAction(() => _wait.value = false);
+      finish();
+      // ignore: avoid_catches_without_on_clauses
+    } catch (e) {
+      log.shout('Caught exception during login: $e');
+      runInAction(() {
+        _wait.value = false;
+        _authError.value = authService.errorFromException(e);
+      });
+    }
+  }
 
   @override
   void shutdown() => deviceService.shutdown();
+
+  @override
+  void factoryReset() => authService.factoryReset();
+
+  bool _ermineReady = false;
+  void _onErmineShellReady() {
+    _ermineReady = true;
+  }
+
+  void _onErmineShellExit() {
+    _ermineReady = false;
+
+    runInAction(() => _loginDone.value = false);
+
+    // Define a local method to run after logout below.
+    void postLogout() {
+      // If OOBE is disabled, perform empty password re-login.
+      if (!launchOobe) {
+        _performNullLogin().then((_) {
+          runInAction(() {
+            shellService.disposeErmineShell();
+            _ermineViewConnection.value = null;
+            _loginDone.value = true;
+          });
+        });
+      } else {
+        // Display login screen again.
+        runInAction(() {
+          shellService.disposeErmineShell();
+          _ermineViewConnection.value = null;
+        });
+      }
+    }
+
+    // Logout and call [postLogout] on both success and error case.
+    authService.logout().then((_) => postLogout()).catchError((e) {
+      log.shout('Caught exception during logout: $e');
+      postLogout();
+    });
+  }
+
+  // TODO(http://fxb/85576): Remove once login and OOBE are mandatory.
+  // If we are skipping OOBE, authenticate using empty password.
+  Future<void> _performNullLogin() async {
+    log.info('Skipped OOBE, authenticating with empty password.');
+    try {
+      authService.hasAccount
+          ? await authService.loginWithPassword('')
+          : await authService.createAccountWithPassword('');
+      // ignore: avoid_catches_without_on_clauses
+    } catch (e) {
+      log.shout('Account found: ${authService.hasAccount}.'
+          ' Caught exception during authentication: $e');
+    }
+  }
+
+  void _onInspect(Node node) {
+    node.boolProperty('ready')!.setValue(ready);
+    node.boolProperty('launchOOBE')!.setValue(launchOobe);
+    node.boolProperty('ermineReady')!.setValue(_ermineReady);
+    node.boolProperty('authenticated')!.setValue(loginDone);
+    if (hasAccount) {
+      node.stringProperty('screen')!.setValue('login');
+    } else {
+      node.stringProperty('screen')!.setValue(screen.name);
+    }
+  }
 }
diff --git a/session_shells/ermine/login/lib/src/widgets/app.dart b/session_shells/ermine/login/lib/src/widgets/app.dart
index 23c976e..1ca4d5f 100644
--- a/session_shells/ermine/login/lib/src/widgets/app.dart
+++ b/session_shells/ermine/login/lib/src/widgets/app.dart
@@ -14,9 +14,7 @@
     as supported_locales;
 import 'package:intl/intl.dart';
 import 'package:login/src/states/oobe_state.dart';
-import 'package:login/src/widgets/ermine.dart';
-// TODO(http://fxb/81598): Uncomment once login  is ready.
-// import 'package:login/src/widgets/login.dart';
+import 'package:login/src/widgets/login.dart';
 import 'package:login/src/widgets/oobe.dart';
 
 class OobeApp extends StatelessWidget {
@@ -38,11 +36,10 @@
         locale: locale,
         localizationsDelegates: [
           localizations.delegate(),
-          GlobalMaterialLocalizations.delegate,
+          ...GlobalMaterialLocalizations.delegates,
           GlobalWidgetsLocalizations.delegate,
         ],
         supportedLocales: supported_locales.locales,
-        shortcuts: FuchsiaKeyboard.defaultShortcuts,
         scrollBehavior: MaterialScrollBehavior().copyWith(
           dragDevices: {PointerDeviceKind.mouse, PointerDeviceKind.touch},
         ),
@@ -52,11 +49,9 @@
           return Material(
             type: MaterialType.canvas,
             child: Observer(builder: (_) {
-              return WidgetFactory.create(() => oobe.launchOobe
-                  ? Oobe(oobe, onFinish: oobe.finish)
-                  // TODO(http://fxb/81598): Uncomment once login  is ready.
-                  // : Login(oobe));
-                  : ErmineApp(oobe));
+              return oobe.hasAccount
+                  ? WidgetFactory.create(() => Login(oobe))
+                  : WidgetFactory.create(() => Oobe(oobe));
             }),
           );
         }),
diff --git a/session_shells/ermine/login/lib/src/widgets/ermine.dart b/session_shells/ermine/login/lib/src/widgets/ermine.dart
index badb8d2..7b209ef 100644
--- a/session_shells/ermine/login/lib/src/widgets/ermine.dart
+++ b/session_shells/ermine/login/lib/src/widgets/ermine.dart
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 import 'package:flutter/material.dart';
+import 'package:flutter_mobx/flutter_mobx.dart';
 import 'package:fuchsia_scenic_flutter/fuchsia_view.dart';
 import 'package:login/src/states/oobe_state.dart';
 
@@ -14,6 +15,8 @@
 
   @override
   Widget build(BuildContext context) {
-    return FuchsiaView(controller: oobe.ermineViewConnection);
+    return Observer(builder: (_) {
+      return FuchsiaView(controller: oobe.ermineViewConnection);
+    });
   }
 }
diff --git a/session_shells/ermine/login/lib/src/widgets/login.dart b/session_shells/ermine/login/lib/src/widgets/login.dart
index ec297fa..04a827f 100644
--- a/session_shells/ermine/login/lib/src/widgets/login.dart
+++ b/session_shells/ermine/login/lib/src/widgets/login.dart
@@ -7,6 +7,7 @@
 import 'package:flutter_mobx/flutter_mobx.dart';
 import 'package:internationalization/strings.dart';
 import 'package:login/src/states/oobe_state.dart';
+import 'package:mobx/mobx.dart';
 
 /// Width of the password field widget.
 const double kOobeBodyFieldWidth = 492;
@@ -18,6 +19,7 @@
   final _formState = GlobalKey<FormState>();
   final _passwordController = TextEditingController();
   final _showPassword = false.asObservable();
+  final _focusNode = FocusNode();
 
   Login(this.oobe);
 
@@ -25,167 +27,161 @@
   Widget build(BuildContext context) {
     return Material(
       color: Color.fromARGB(0xff, 0x0c, 0x0c, 0x0c),
-      child: Column(
-        crossAxisAlignment: CrossAxisAlignment.stretch,
-        children: [
-          // Header: Fuchsia logo and welcome.
-          SizedBox(
-            height: 200,
-            child: Row(
-              mainAxisAlignment: MainAxisAlignment.center,
-              crossAxisAlignment: CrossAxisAlignment.end,
-              children: [
-                // Fuchsia logo.
-                Image(
-                  image: AssetImage('images/Fuchsia-logo-2x.png'),
-                  color: Theme.of(context).colorScheme.primary,
-                  width: 24,
-                  height: 24,
-                ),
-                SizedBox(width: 16),
-                // Welcome text.
-                Text(
-                  Strings.fuchsiaWelcome,
-                  style: Theme.of(context).textTheme.headline6,
-                ),
-              ],
-            ),
-          ),
-
-          // Body: Oobe screens.
-          Expanded(
-            child: Padding(
-              padding: EdgeInsets.all(16),
-              child: FocusScope(
-                child: Observer(builder: (context) {
-                  return Column(
-                    crossAxisAlignment: CrossAxisAlignment.center,
-                    children: [
-                      // Password.
-                      Expanded(
-                        child: Form(
-                          key: _formState,
-                          child: Column(
-                            mainAxisAlignment: MainAxisAlignment.center,
-                            crossAxisAlignment: CrossAxisAlignment.start,
-                            children: [
-                              // Title.
-                              Text(
-                                Strings.login,
-                                style: Theme.of(context).textTheme.headline3,
+      child: Center(
+        child: FocusScope(
+          child: Observer(builder: (context) {
+            return Form(
+              onChanged: oobe.resetAuthError,
+              key: _formState,
+              child: Column(
+                mainAxisAlignment: MainAxisAlignment.center,
+                crossAxisAlignment: CrossAxisAlignment.start,
+                children: [
+                  // Title.
+                  Padding(
+                    padding: EdgeInsets.only(left: 16),
+                    child: Text(
+                      Strings.login,
+                      style: Theme.of(context).textTheme.headline3,
+                    ),
+                  ),
+                  SizedBox(height: 36),
+                  // Password.
+                  SizedBox(
+                    width: kOobeBodyFieldWidth,
+                    height: 92,
+                    child: Padding(
+                      padding: EdgeInsets.only(left: 16),
+                      child: Row(
+                        crossAxisAlignment: CrossAxisAlignment.start,
+                        children: [
+                          Expanded(
+                            child: TextFormField(
+                              key: ValueKey('password'),
+                              focusNode: _focusNode,
+                              autofocus: true,
+                              autovalidateMode:
+                                  AutovalidateMode.onUserInteraction,
+                              controller: _passwordController,
+                              obscureText: !_showPassword.value,
+                              decoration: InputDecoration(
+                                border: OutlineInputBorder(),
+                                labelText: Strings.passwordHint,
+                                errorText: oobe.authError.isNotEmpty
+                                    ? oobe.authError
+                                    : null,
                               ),
-                              SizedBox(height: 40),
-                              // Password.
-                              SizedBox(
-                                width: kOobeBodyFieldWidth,
-                                child: TextFormField(
-                                  autofocus: true,
-                                  controller: _passwordController,
-                                  obscureText: !_showPassword.value,
-                                  decoration: InputDecoration(
-                                    border: OutlineInputBorder(),
-                                    labelText: Strings.passwordHint,
+                              validator: (value) {
+                                if (value == null || value.isEmpty) {
+                                  return Strings.accountPasswordInvalid;
+                                }
+                                if (oobe.authError.isNotEmpty) {
+                                  Focus.of(context).requestFocus(_focusNode);
+                                }
+                                return null;
+                              },
+                              onFieldSubmitted: (_) {
+                                if (_validate()) {
+                                  oobe.login(_passwordController.text);
+                                }
+                              },
+                            ),
+                          ),
+                          SizedBox(width: 16),
+                          Container(
+                            width: 56,
+                            height: 56,
+                            color: oobe.wait
+                                ? Theme.of(context).disabledColor
+                                : Colors.white,
+                            child: oobe.wait
+                                ? Center(child: CircularProgressIndicator())
+                                : ElevatedButton(
+                                    key: ValueKey('login'),
+                                    child: Icon(Icons.arrow_forward),
+                                    onPressed: () => _validate() && !oobe.wait
+                                        ? oobe.login(_passwordController.text)
+                                        : null,
                                   ),
-                                  validator: (value) {
-                                    // TODO(http://fxb/81598): Uncomment once
-                                    // login functionality is ready.
-                                    // if (value == null ||
-                                    //     value.isEmpty ||
-                                    //     value.length < passwordLength) {
-                                    //   return Strings.accountPasswordInvalid;
-                                    // }
-                                    return null;
-                                  },
-                                  onFieldSubmitted: (_) {
-                                    if (_validate()) {
-                                      oobe.login(_passwordController.text);
-                                    }
-                                  },
-                                ),
-                              ),
-                              SizedBox(height: 40),
+                          ),
+                        ],
+                      ),
+                    ),
+                  ),
 
-                              // Show password checkbox.
-                              SizedBox(
-                                width: kOobeBodyFieldWidth,
-                                child: Row(
-                                  crossAxisAlignment: CrossAxisAlignment.center,
-                                  children: [
-                                    Checkbox(
-                                      onChanged: (value) =>
-                                          _showPassword.value = value == true,
-                                      value: _showPassword.value,
-                                    ),
-                                    SizedBox(height: 40),
-                                    Text(Strings.showPassword)
-                                  ],
-                                ),
-                              ),
-                              SizedBox(height: 40),
+                  // Show password checkbox.
+                  SizedBox(
+                    width: kOobeBodyFieldWidth,
+                    child: Row(
+                      crossAxisAlignment: CrossAxisAlignment.center,
+                      children: [
+                        Checkbox(
+                          onChanged: (value) => runInAction(
+                              () => _showPassword.value = value == true),
+                          value: _showPassword.value,
+                        ),
+                        SizedBox(height: 40),
+                        Text(Strings.showPassword)
+                      ],
+                    ),
+                  ),
+                  SizedBox(height: 144),
 
-                              // Factory reset.
-                              SizedBox(
-                                width: kOobeBodyFieldWidth,
-                                child: Row(
-                                  crossAxisAlignment: CrossAxisAlignment.center,
-                                  children: [
-                                    TextButton(
-                                      onPressed: () {},
-                                      child: Text(
-                                        Strings.factoryDataReset,
-                                        style: TextStyle(
-                                          decoration: TextDecoration.underline,
-                                        ),
-                                      ),
-                                    ),
-                                    SizedBox(width: 10),
-                                    IconButton(
-                                      icon: Icon(Icons.help_outline),
-                                      // TODO(http://fxb/81598): Implement as a
-                                      // tooltip.
-                                      onPressed: () {},
-                                    ),
-                                  ],
-                                ),
-                              )
-                            ],
+                  Container(
+                    alignment: Alignment.centerLeft,
+                    padding: EdgeInsets.only(left: 16),
+                    width: kOobeBodyFieldWidth,
+                    child: TextButton(
+                      style: TextButton.styleFrom(padding: EdgeInsets.zero),
+                      onPressed: () => _confirmFactoryReset(context),
+                      child: Container(
+                        padding: EdgeInsets.only(bottom: 1),
+                        decoration: BoxDecoration(
+                          border: Border(
+                            bottom: BorderSide(
+                              color: Colors.white,
+                              width: 1,
+                            ),
                           ),
                         ),
+                        child: Text(Strings.factoryDataReset),
                       ),
-
-                      // Buttons.
-                      Container(
-                        alignment: Alignment.center,
-                        padding: EdgeInsets.all(24),
-                        child: Row(
-                          mainAxisAlignment: MainAxisAlignment.center,
-                          children: [
-                            // Shutdown button.
-                            OutlinedButton(
-                              onPressed: oobe.shutdown,
-                              child: Text(Strings.shutdown.toUpperCase()),
-                            ),
-                            SizedBox(width: 24),
-                            // Login button.
-                            ElevatedButton(
-                              onPressed: () => _validate()
-                                  ? oobe.login(_passwordController.text)
-                                  : null,
-                              child: Text(Strings.login.toUpperCase()),
-                            ),
-                          ],
-                        ),
-                      ),
-                    ],
-                  );
-                }),
+                    ),
+                  ),
+                ],
               ),
-            ),
-          ),
-        ],
+            );
+          }),
+        ),
       ),
     );
   }
 
   bool _validate() => _formState.currentState?.validate() ?? false;
+
+  void _confirmFactoryReset(BuildContext context) {
+    showDialog(
+      context: context,
+      builder: (context) => AlertDialog(
+        title: SizedBox(
+          width: 672,
+          child: Text(Strings.factoryDataResetPrompt),
+        ),
+        actions: [
+          OutlinedButton(
+            child: Text(Strings.cancel.toUpperCase()),
+            onPressed: () => Navigator.pop(context, false),
+          ),
+          ElevatedButton(
+            child: Text(Strings.eraseAndReset.toUpperCase()),
+            onPressed: () => Navigator.pop(context, true),
+          ),
+        ],
+      ),
+    ).then((erase) {
+      if (erase) {
+        oobe.factoryReset();
+      }
+    });
+  }
 }
diff --git a/session_shells/ermine/login/lib/src/widgets/oobe.dart b/session_shells/ermine/login/lib/src/widgets/oobe.dart
index 76fc4bc..794aa58 100644
--- a/session_shells/ermine/login/lib/src/widgets/oobe.dart
+++ b/session_shells/ermine/login/lib/src/widgets/oobe.dart
@@ -7,19 +7,18 @@
 import 'package:internationalization/strings.dart';
 import 'package:login/src/states/oobe_state.dart';
 import 'package:login/src/widgets/password.dart';
-import 'package:login/src/widgets/channels.dart';
 import 'package:login/src/widgets/ready.dart';
 // TODO(fxbug.dev/73407): Skip data sharing screen until privacy policy is
 // finalized.
+// import 'package:login/src/widgets/channels.dart';
 // import 'package:login/src/widgets/data_sharing.dart';
-import 'package:login/src/widgets/ssh_keys.dart';
+// import 'package:login/src/widgets/ssh_keys.dart';
 
 /// Defines a widget that handles the OOBE flow.
 class Oobe extends StatelessWidget {
   final OobeState oobe;
-  final VoidCallback onFinish;
 
-  const Oobe(this.oobe, {required this.onFinish});
+  const Oobe(this.oobe);
 
   @override
   Widget build(BuildContext context) {
@@ -56,14 +55,14 @@
           Expanded(
             child: Observer(builder: (context) {
               switch (oobe.screen) {
-                case OobeScreen.channel:
-                  return Channels(oobe);
                 // TODO(fxbug.dev/73407): Skip data sharing screen until privacy
                 // policy is finalized.
+                // case OobeScreen.channel:
+                //   return Channels(oobe);
                 // case OobeScreen.dataSharing:
                 //   return DataSharing(oobe);
-                case OobeScreen.sshKeys:
-                  return SshKeys(oobe);
+                // case OobeScreen.sshKeys:
+                //   return SshKeys(oobe);
                 case OobeScreen.password:
                   return Password(oobe);
                 case OobeScreen.done:
@@ -79,7 +78,7 @@
               crossAxisAlignment: CrossAxisAlignment.start,
               mainAxisAlignment: MainAxisAlignment.center,
               children: [
-                for (var index = OobeScreen.channel.index;
+                for (var index = OobeScreen.password.index;
                     index <= OobeScreen.done.index;
                     index++)
                   Padding(
diff --git a/session_shells/ermine/login/lib/src/widgets/password.dart b/session_shells/ermine/login/lib/src/widgets/password.dart
index 7798674..fbab7e8 100644
--- a/session_shells/ermine/login/lib/src/widgets/password.dart
+++ b/session_shells/ermine/login/lib/src/widgets/password.dart
@@ -9,6 +9,7 @@
 import 'package:login/src/states/oobe_state.dart';
 import 'package:login/src/widgets/header.dart';
 import 'package:login/src/widgets/login.dart';
+import 'package:mobx/mobx.dart';
 
 /// Defines a widget to create account password.
 class Password extends StatelessWidget {
@@ -49,7 +50,9 @@
                       SizedBox(
                         width: kOobeBodyFieldWidth,
                         child: TextFormField(
+                          key: ValueKey('password1'),
                           autofocus: true,
+                          autovalidateMode: AutovalidateMode.onUserInteraction,
                           controller: _passwordController,
                           obscureText: !_showPassword.value,
                           decoration: InputDecoration(
@@ -57,13 +60,11 @@
                             labelText: Strings.passwordHint,
                           ),
                           validator: (value) {
-                            // TODO(http://fxb/81598): Uncomment once
-                            // login functionality is ready.
-                            // if (value == null ||
-                            //     value.isEmpty ||
-                            //     value.length < passwordLength) {
-                            //   return Strings.accountPasswordInvalid;
-                            // }
+                            if (value == null ||
+                                value.isEmpty ||
+                                value.length < kPasswordLength) {
+                              return Strings.accountPasswordInvalid;
+                            }
                             return null;
                           },
                         ),
@@ -73,6 +74,8 @@
                       SizedBox(
                         width: kOobeBodyFieldWidth,
                         child: TextFormField(
+                          key: ValueKey('password2'),
+                          autovalidateMode: AutovalidateMode.onUserInteraction,
                           controller: _confirmPasswordController,
                           obscureText: !_showPassword.value,
                           decoration: InputDecoration(
@@ -80,16 +83,9 @@
                             labelText: Strings.confirmPasswordHint,
                           ),
                           validator: (value) {
-                            // TODO(http://fxb/81598): Uncomment once
-                            // login functionality is ready.
-                            // if (value == null ||
-                            //     value.isEmpty ||
-                            //     value.length < passwordLength) {
-                            //   return Strings.accountPasswordInvalid;
-                            // }
-                            // if (value != _passwordController.text) {
-                            //   return Strings.accountPasswordMismatch;
-                            // }
+                            if (value != _passwordController.text) {
+                              return Strings.accountPasswordMismatch;
+                            }
                             return null;
                           },
                           onFieldSubmitted: (value) =>
@@ -104,15 +100,30 @@
                           crossAxisAlignment: CrossAxisAlignment.center,
                           children: [
                             Checkbox(
-                              onChanged: (value) =>
-                                  _showPassword.value = value == true,
+                              onChanged: (value) => runInAction(
+                                  () => _showPassword.value = value == true),
                               value: _showPassword.value,
                             ),
                             SizedBox(height: 40),
                             Text(Strings.showPassword)
                           ],
                         ),
-                      )
+                      ),
+                      SizedBox(height: 40),
+                      // Show spinning indicator if waiting or api errors,
+                      // if any.
+                      SizedBox(
+                        height: 40,
+                        width: kOobeBodyFieldWidth,
+                        child: oobe.wait
+                            ? Center(child: CircularProgressIndicator())
+                            : oobe.authError.isNotEmpty
+                                ? Text(
+                                    oobe.authError,
+                                    style: TextStyle(color: Colors.red),
+                                  )
+                                : Offstage(),
+                      ),
                     ],
                   ),
                 ),
@@ -127,13 +138,14 @@
                   children: [
                     // Back button.
                     OutlinedButton(
-                      onPressed: oobe.prevScreen,
+                      onPressed: oobe.wait ? null : oobe.prevScreen,
                       child: Text(Strings.back.toUpperCase()),
                     ),
                     SizedBox(width: 24),
                     // Set password button.
                     ElevatedButton(
-                      onPressed: () => _validate()
+                      key: ValueKey('setPassword'),
+                      onPressed: () => _validate() && !oobe.wait
                           ? oobe.setPassword(_confirmPasswordController.text)
                           : null,
                       child: Text(Strings.setPassword.toUpperCase()),
diff --git a/session_shells/ermine/login/lib/src/widgets/ready.dart b/session_shells/ermine/login/lib/src/widgets/ready.dart
index 4a2134a..7519f1f 100644
--- a/session_shells/ermine/login/lib/src/widgets/ready.dart
+++ b/session_shells/ermine/login/lib/src/widgets/ready.dart
@@ -3,7 +3,6 @@
 // found in the LICENSE file.
 
 import 'package:flutter/material.dart';
-import 'package:flutter_mobx/flutter_mobx.dart';
 import 'package:internationalization/strings.dart';
 import 'package:login/src/states/oobe_state.dart';
 
@@ -18,55 +17,54 @@
     return Padding(
       padding: EdgeInsets.all(16),
       child: FocusScope(
-        child: Observer(builder: (context) {
-          return Column(
-            crossAxisAlignment: CrossAxisAlignment.stretch,
-            children: [
-              // Title.
-              Text(
-                Strings.passwordIsSet,
-                textAlign: TextAlign.center,
-                style: Theme.of(context).textTheme.headline3,
-              ),
+        child: Column(
+          crossAxisAlignment: CrossAxisAlignment.stretch,
+          children: [
+            // Title.
+            Text(
+              Strings.passwordIsSet,
+              textAlign: TextAlign.center,
+              style: Theme.of(context).textTheme.headline3,
+            ),
 
-              // Description.
-              Container(
-                alignment: Alignment.center,
-                padding: EdgeInsets.all(24),
-                child: SizedBox(
-                  width: 600,
-                  child: Text(
-                    Strings.readyToUse,
-                    textAlign: TextAlign.center,
-                    style: Theme.of(context)
-                        .textTheme
-                        .bodyText1!
-                        .copyWith(height: 1.55),
+            // Description.
+            Container(
+              alignment: Alignment.center,
+              padding: EdgeInsets.all(24),
+              child: SizedBox(
+                width: 600,
+                child: Text(
+                  Strings.readyToUse,
+                  textAlign: TextAlign.center,
+                  style: Theme.of(context)
+                      .textTheme
+                      .bodyText1!
+                      .copyWith(height: 1.55),
+                ),
+              ),
+            ),
+
+            // Empty.
+            Expanded(child: Container()),
+
+            // Start workstation button.
+            Container(
+              alignment: Alignment.center,
+              padding: EdgeInsets.all(24),
+              child: Row(
+                mainAxisAlignment: MainAxisAlignment.center,
+                children: [
+                  OutlinedButton(
+                    key: ValueKey('startWorkstation'),
+                    autofocus: true,
+                    onPressed: oobe.finish,
+                    child: Text(Strings.startWorkstation.toUpperCase()),
                   ),
-                ),
+                ],
               ),
-
-              // Empty.
-              Expanded(child: Container()),
-
-              // Start workstation button.
-              Container(
-                alignment: Alignment.center,
-                padding: EdgeInsets.all(24),
-                child: Row(
-                  mainAxisAlignment: MainAxisAlignment.center,
-                  children: [
-                    OutlinedButton(
-                      autofocus: true,
-                      onPressed: oobe.finish,
-                      child: Text(Strings.startWorkstation.toUpperCase()),
-                    ),
-                  ],
-                ),
-              ),
-            ],
-          );
-        }),
+            ),
+          ],
+        ),
       ),
     );
   }
diff --git a/session_shells/ermine/login/lib/test_main.dart b/session_shells/ermine/login/lib/test_main.dart
new file mode 100644
index 0000000..c4797e4
--- /dev/null
+++ b/session_shells/ermine/login/lib/test_main.dart
@@ -0,0 +1,14 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter_driver/driver_extension.dart';
+
+import 'main.dart' as entrypoint;
+
+Future<void> main() async {
+  // Instrument flutter driver handler to invoke shortcut actions.
+  enableFlutterDriverExtension(enableTextEntryEmulation: false);
+
+  return entrypoint.main();
+}
diff --git a/session_shells/ermine/login/meta/login.cml b/session_shells/ermine/login/meta/login.cml
new file mode 100644
index 0000000..274d3e2
--- /dev/null
+++ b/session_shells/ermine/login/meta/login.cml
@@ -0,0 +1,169 @@
+{
+    include: [
+        "//sdk/lib/inspect/client.shard.cml",
+
+        // Enable system logging.
+        "syslog/client.shard.cml",
+    ],
+    program: {
+        args: [ "--expose_dirs=hosted_directories" ],
+        data: "data/login",
+    },
+    children: [
+        {
+            name: "ermine_shell",
+            url: "fuchsia-pkg://fuchsia.com/ermine#meta/ermine.cm",
+            startup: "lazy",
+        },
+    ],
+    capabilities: [
+        {
+            protocol: [ "fuchsia.ui.app.ViewProvider" ],
+        },
+        {
+            directory: "account_data_dir",
+            rights: [ "rw*" ],
+            path: "/hosted_directories/account_data",
+        },
+        {
+            directory: "account_cache_dir",
+            rights: [ "rw*" ],
+            path: "/hosted_directories/account_cache",
+        },
+        {
+            storage: "account",
+            from: "self",
+            backing_dir: "account_data_dir",
+            storage_id: "static_instance_id_or_moniker",
+        },
+        {
+            storage: "account_cache",
+            from: "self",
+            backing_dir: "account_cache_dir",
+            storage_id: "static_instance_id_or_moniker",
+        },
+    ],
+    use: [
+        {
+            protocol: "fuchsia.component.Realm",
+            from: "framework",
+        },
+        {
+            protocol: [
+                "fuchsia.accessibility.semantics.SemanticsManager",
+                "fuchsia.cobalt.LoggerFactory",
+                "fuchsia.fonts.Provider",
+                "fuchsia.hardware.power.statecontrol.Admin",
+                "fuchsia.identity.account.AccountManager",
+                "fuchsia.intl.PropertyProvider",
+                "fuchsia.recovery.FactoryReset",
+                "fuchsia.settings.Intl",
+                "fuchsia.settings.Privacy",
+                "fuchsia.ssh.AuthorizedKeys",
+                "fuchsia.ui.scenic.Scenic",
+                "fuchsia.update.channelcontrol.ChannelControl",
+            ],
+        },
+        {
+            directory: "config-data",
+            from: "parent",
+            rights: [ "r*" ],
+            path: "/config/data",
+        },
+    ],
+    offer: [
+        {
+            protocol: [
+                "fuchsia.accessibility.semantics.SemanticsManager",
+                "fuchsia.buildinfo.Provider",
+                "fuchsia.cobalt.LoggerFactory",
+                "fuchsia.element.Manager",
+                "fuchsia.feedback.CrashReporter",
+                "fuchsia.fonts.Provider",
+                "fuchsia.hardware.power.statecontrol.Admin",
+                "fuchsia.intl.PropertyProvider",
+                "fuchsia.logger.LogSink",
+                "fuchsia.media.Audio",
+                "fuchsia.media.AudioCore",
+                "fuchsia.media.AudioDeviceEnumerator",
+                "fuchsia.media.ProfileProvider",
+                "fuchsia.memory.Monitor",
+                "fuchsia.memorypressure.Provider",
+                "fuchsia.net.interfaces.State",
+                "fuchsia.net.name.Lookup",
+                "fuchsia.posix.socket.Provider",
+                "fuchsia.power.battery.BatteryManager",
+                "fuchsia.process.Launcher",
+                "fuchsia.settings.Intl",
+                "fuchsia.settings.Keyboard",
+                "fuchsia.settings.Privacy",
+                "fuchsia.ssh.AuthorizedKeys",
+                "fuchsia.sys.Launcher",
+                "fuchsia.sysmem.Allocator",
+                "fuchsia.tracing.provider.Registry",
+                "fuchsia.ui.activity.Provider",
+                "fuchsia.ui.activity.Tracker",
+                "fuchsia.ui.brightness.Control",
+                "fuchsia.ui.composition.Allocator",
+                "fuchsia.ui.composition.Flatland",
+                "fuchsia.ui.focus.FocusChainListenerRegistry",
+                "fuchsia.ui.input.ImeService",
+                "fuchsia.ui.input.InputDeviceRegistry",
+                "fuchsia.ui.input.PointerCaptureListenerRegistry",
+                "fuchsia.ui.input3.Keyboard",
+                "fuchsia.ui.scenic.Scenic",
+                "fuchsia.ui.shortcut.Registry",
+                "fuchsia.update.channelcontrol.ChannelControl",
+                "fuchsia.update.Manager",
+                "fuchsia.vulkan.loader.Loader",
+                "fuchsia.wlan.common",
+                "fuchsia.wlan.policy",
+                "fuchsia.wlan.policy.ClientProvider",
+            ],
+            from: "parent",
+            to: [ "#ermine_shell" ],
+        },
+        {
+            directory: "config-data",
+            from: "parent",
+            to: "#ermine_shell",
+        },
+        {
+            directory: "root-ssl-certificates",
+            from: "parent",
+            to: [ "#ermine_shell" ],
+        },
+        {
+            storage: "account",
+            from: "self",
+            to: "#ermine_shell",
+        },
+        {
+            // TODO(fxbug.dev/89628): This cache does not currently have any
+            // process deleting files when it gets full, meaning all clients
+            // need to place constraints on their usage. This is part of a wider
+            // question on cache policy management discussed in fxb/89628.
+            storage: "account_cache",
+            from: "self",
+            to: "#ermine_shell",
+        },
+        {
+            storage: "tmp",
+            from: "parent",
+            to: "#ermine_shell",
+        },
+    ],
+    expose: [
+        {
+            protocol: [ "fuchsia.ui.app.ViewProvider" ],
+            from: "self",
+        },
+        {
+            protocol: [
+                "fuchsia.element.GraphicalPresenter",
+                "fuchsia.element.Manager",
+            ],
+            from: "#ermine_shell",
+        },
+    ],
+}
diff --git a/session_shells/ermine/login/meta/login.cmx b/session_shells/ermine/login/meta/login.cmx
deleted file mode 100644
index d6fdc3c..0000000
--- a/session_shells/ermine/login/meta/login.cmx
+++ /dev/null
@@ -1,32 +0,0 @@
-{
-    "program": {
-        "data": "data/login"
-    },
-    "sandbox": {
-        "features": [
-            "config-data",
-            "isolated-persistent-storage"
-        ],
-        "services": [
-            "fuchsia.accessibility.semantics.SemanticsManager",
-            "fuchsia.cobalt.LoggerFactory",
-            "fuchsia.device.manager.Administrator",
-            "fuchsia.element.Manager",
-            "fuchsia.fonts.Provider",
-            "fuchsia.intl.PropertyProvider",
-            "fuchsia.logger.LogSink",
-            "fuchsia.settings.Intl",
-            "fuchsia.settings.Privacy",
-            "fuchsia.ssh.AuthorizedKeys",
-            "fuchsia.sys.Environment",
-            "fuchsia.sys.Launcher",
-            "fuchsia.ui.focus.FocusChainListenerRegistry",
-            "fuchsia.ui.input.ImeService",
-            "fuchsia.ui.input.InputDeviceRegistry",
-            "fuchsia.ui.input3.Keyboard",
-            "fuchsia.ui.scenic.Scenic",
-            "fuchsia.ui.views.ViewRefInstalled",
-            "fuchsia.update.channelcontrol.ChannelControl"
-        ]
-    }
-}
\ No newline at end of file
diff --git a/session_shells/ermine/memfs/BUILD.gn b/session_shells/ermine/memfs/BUILD.gn
new file mode 100644
index 0000000..1f56eb7
--- /dev/null
+++ b/session_shells/ermine/memfs/BUILD.gn
@@ -0,0 +1,14 @@
+# Copyright 2022 The Fuchsia Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+import("//build/components.gni")
+
+fuchsia_component("component") {
+  component_name = "memfs"
+  deps = [ "//src/sys/component_manager/tests/memfs" ]
+  manifest = "meta/memfs.cml"
+}
+
+fuchsia_package("memfs") {
+  deps = [ ":component" ]
+}
diff --git a/session_shells/ermine/memfs/README.md b/session_shells/ermine/memfs/README.md
new file mode 100644
index 0000000..c8800ce
--- /dev/null
+++ b/session_shells/ermine/memfs/README.md
@@ -0,0 +1,7 @@
+## memfs
+
+This component exposes a directory capability, `memfs`, backed by the
+in-memory [MemFS filesystem](https://cs.opensource.google/fuchsia/fuchsia/+/main:src/storage/memfs/README.md).
+
+The implementation lives in [//src/sys/component_manager/tests/memfs](https://cs.opensource.google/fuchsia/fuchsia/+/main:src/sys/component_manager/tests/memfs)
+
diff --git a/session_shells/ermine/memfs/meta/memfs.cml b/session_shells/ermine/memfs/meta/memfs.cml
new file mode 100644
index 0000000..6952ea4
--- /dev/null
+++ b/session_shells/ermine/memfs/meta/memfs.cml
@@ -0,0 +1,23 @@
+{
+    include: [
+        "syslog/client.shard.cml",
+        "syslog/elf_stdio.shard.cml",
+    ],
+    program: {
+        runner: "elf",
+        binary: "bin/memfs",
+    },
+    capabilities: [
+        {
+            directory: "memfs",
+            rights: [ "rw*" ],
+            path: "/svc/fuchsia.io.Directory",
+        },
+    ],
+    expose: [
+        {
+            directory: "memfs",
+            from: "self",
+        },
+    ],
+}
diff --git a/session_shells/ermine/session/BUILD.gn b/session_shells/ermine/session/BUILD.gn
index d4abeab..06b1f5a 100644
--- a/session_shells/ermine/session/BUILD.gn
+++ b/session_shells/ermine/session/BUILD.gn
@@ -3,76 +3,69 @@
 # found in the LICENSE file.
 
 import("//build/components.gni")
-import("//build/rust/rustc_binary.gni")
-import("//src/session/build/session_config.gni")
+import("//build/dart/dart_component.gni")
+import("//build/dart/dart_library.gni")
+import("//src/session/build/session_manager.gni")
 
-group("tests") {
-  testonly = true
-  deps = [ ":workstation_session_tests" ]
-}
-
-rustc_binary("bin") {
-  output_name = "workstation_session"
-  with_unit_tests = true
-  edition = "2018"
-
+dart_library("lib") {
+  package_name = "workstation_session"
+  source_dir = "lib"
+  null_safe = true
+  sources = [ "main.dart" ]
   deps = [
-    "//sdk/fidl/fuchsia.component:fuchsia.component-rustc",
-    "//sdk/fidl/fuchsia.element:fuchsia.element-rustc",
-    "//sdk/fidl/fuchsia.identity.account:fuchsia.identity.account-rustc",
-    "//sdk/fidl/fuchsia.input.report:fuchsia.input.report-rustc",
-    "//sdk/fidl/fuchsia.io:fuchsia.io-rustc",
-    "//sdk/fidl/fuchsia.session.scene:fuchsia.session.scene-rustc",
-    "//sdk/fidl/fuchsia.sys:fuchsia.sys-rustc",
-    "//sdk/fidl/fuchsia.sys2:fuchsia.sys2-rustc",
-    "//sdk/fidl/fuchsia.ui.app:fuchsia.ui.app-rustc",
-    "//sdk/fidl/fuchsia.ui.gfx:fuchsia.ui.gfx-rustc",
-    "//sdk/fidl/fuchsia.ui.input:fuchsia.ui.input-rustc",
-    "//sdk/fidl/fuchsia.ui.scenic:fuchsia.ui.scenic-rustc",
-    "//sdk/fidl/fuchsia.ui.shortcut:fuchsia.ui.shortcut-rustc",
-    "//sdk/fidl/fuchsia.ui.views:fuchsia.ui.views-rustc",
-    "//src/lib/fidl/rust/fidl",
-    "//src/lib/fuchsia-async",
-    "//src/lib/fuchsia-component",
-    "//src/lib/syslog/rust:syslog",
-    "//src/lib/ui/fuchsia-scenic",
-    "//src/lib/ui/input-synthesis",
-    "//src/lib/zircon/rust:fuchsia-zircon",
-    "//src/session/lib/element_management",
-    "//src/ui/lib/input_pipeline",
-    "//third_party/rust_crates:anyhow",
-    "//third_party/rust_crates:async-trait",
-    "//third_party/rust_crates:futures",
-    "//third_party/rust_crates:log",
-    "//third_party/rust_crates:matches",
-    "//third_party/rust_crates:rand",
-    "//third_party/rust_crates:uuid",
+    "//sdk/dart/fidl",
+    "//sdk/dart/fuchsia",
+    "//sdk/dart/fuchsia_logger",
+    "//sdk/dart/fuchsia_services",
+    "//sdk/dart/fuchsia_vfs",
+    "//sdk/dart/zircon",
+    "//sdk/fidl/fuchsia.component",
+    "//sdk/fidl/fuchsia.component.decl",
+    "//sdk/fidl/fuchsia.io",
+    "//sdk/fidl/fuchsia.session.scene",
+    "//sdk/fidl/fuchsia.ui.app",
+    "//sdk/fidl/fuchsia.ui.focus",
+    "//sdk/fidl/fuchsia.ui.input",
+    "//sdk/fidl/fuchsia.ui.keyboard.focus",
+    "//sdk/fidl/fuchsia.ui.shortcut",
+    "//sdk/fidl/fuchsia.ui.views",
   ]
-
-  test_deps = []
-
-  sources = [ "src/main.rs" ]
 }
 
-fuchsia_package_with_single_component("workstation_routing") {
-  manifest = "meta/workstation_routing.cml"
-}
-
-fuchsia_package_with_single_component("workstation_session") {
+dart_component("session_component") {
+  component_name = "workstation_session"
   manifest = "meta/workstation_session.cml"
-  deps = [ ":bin" ]
+  deps = [ ":lib" ]
 }
 
-fuchsia_unittest_package("workstation_session_tests") {
-  test_specs = {
-    log_settings = {
-      max_severity = "ERROR"
-    }
+fuchsia_component("workstation_routing") {
+  if (!dart_default_build_cfg.is_aot && !dart_default_build_cfg.is_product) {
+    manifest = "meta/workstation_routing_jit.cml"
+  } else if (!dart_default_build_cfg.is_aot &&
+             dart_default_build_cfg.is_product) {
+    manifest = "meta/workstation_routing_jit_product.cml"
+  } else if (dart_default_build_cfg.is_aot &&
+             !dart_default_build_cfg.is_product) {
+    manifest = "meta/workstation_routing_aot.cml"
+  } else if (dart_default_build_cfg.is_aot &&
+             dart_default_build_cfg.is_product) {
+    manifest = "meta/workstation_routing_aot_product.cml"
   }
-  manifest = "meta/workstation_session_bin_test.cml"
-  deps = [ ":bin_test" ]
 }
 
-session_config("session_config") {
-  config = "//src/experiences/session_shells/ermine/session/session_config.json"
+fuchsia_package("workstation_session_pkg") {
+  package_name = "workstation_session"
+  deps = [
+    ":session_component",
+    ":workstation_routing",
+  ]
+}
+
+session_manager_package("session_manager") {
+  config =
+      "//src/experiences/session_shells/ermine/session/session_config.json5"
+}
+
+group("session") {
+  public_deps = [ ":workstation_session_pkg" ]
 }
diff --git a/session_shells/ermine/session/lib/main.dart b/session_shells/ermine/session/lib/main.dart
new file mode 100644
index 0000000..43a05ec
--- /dev/null
+++ b/session_shells/ermine/session/lib/main.dart
@@ -0,0 +1,97 @@
+// Copyright 2021 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:fidl_fuchsia_component/fidl_async.dart';
+import 'package:fidl_fuchsia_component_decl/fidl_async.dart';
+import 'package:fidl_fuchsia_io/fidl_async.dart';
+import 'package:fidl_fuchsia_session_scene/fidl_async.dart';
+import 'package:fidl_fuchsia_ui_app/fidl_async.dart';
+import 'package:fidl_fuchsia_ui_focus/fidl_async.dart';
+import 'package:fidl_fuchsia_ui_keyboard_focus/fidl_async.dart';
+import 'package:fidl_fuchsia_ui_shortcut/fidl_async.dart' as shortcut;
+import 'package:fidl_fuchsia_ui_views/fidl_async.dart';
+import 'package:fuchsia_logger/logger.dart';
+import 'package:fuchsia_services/services.dart';
+import 'package:zircon/zircon.dart';
+
+const kLoginShellName = 'login_shell';
+const kAccountName = 'created_by_session';
+const kAccountPassword = '';
+const kAccountDirectory = 'account_data';
+
+void main(List args) async {
+  setupLogger(name: 'workstation_session');
+  log.info('Setting up workstation session');
+
+  try {
+    // Connect to the Realm.
+    final realm = RealmProxy();
+    Incoming.fromSvcPath().connectToService(realm);
+
+    // Get the login shell's exposed /svc directory.
+    final exposedDir = DirectoryProxy();
+    await realm.openExposedDir(
+        ChildRef(name: kLoginShellName), exposedDir.ctrl.request());
+
+    // Get the login shell's view provider.
+    final viewProvider = ViewProviderProxy();
+    Incoming.withDirectory(exposedDir).connectToService(viewProvider);
+
+    // Set the login shell's view as the root view.
+    final sceneManager = ManagerProxy();
+    Incoming.fromSvcPath().connectToService(sceneManager);
+    final viewRef = await sceneManager.setRootView(viewProvider.ctrl.unbind());
+
+    // Wait for the view to be attached to the scene.
+    final viewRefInstalled = ViewRefInstalledProxy();
+    Incoming.fromSvcPath().connectToService(viewRefInstalled);
+    await viewRefInstalled.watch(viewRef.duplicate());
+
+    // Set focus on the root view.
+    await sceneManager.requestFocus(viewRef.duplicate());
+
+    // Hook up focus chain to IME and shortcut manager.
+    final focusChainRegistry = FocusChainListenerRegistryProxy();
+    Incoming.fromSvcPath().connectToService(focusChainRegistry);
+    await focusChainRegistry
+        .register(FocusChainListenerBinding().wrap(_FocusChainListener()));
+    // ignore: avoid_catches_without_on_clauses
+  } catch (e) {
+    log.severe('Caught exception during workstation session setup: $e');
+  }
+}
+
+/// Listens to focus chain updates and forwards them to [shortcut.Manager] and
+/// IME [Controller].
+class _FocusChainListener extends FocusChainListener {
+  final _ime = ControllerProxy();
+  final _shortcutManager = shortcut.ManagerProxy();
+
+  _FocusChainListener() {
+    Incoming.fromSvcPath().connectToService(_ime);
+    Incoming.fromSvcPath().connectToService(_shortcutManager);
+  }
+
+  @override
+  Future<void> onFocusChange(FocusChain focusChain) async {
+    final chain = focusChain.focusChain;
+    if (chain == null || chain.isEmpty) {
+      return;
+    }
+
+    try {
+      final viewRef = chain.last.duplicate();
+      await _ime.notify(viewRef);
+      await _shortcutManager.handleFocusChange(focusChain);
+      // ignore: avoid_catches_without_on_clauses
+    } catch (e) {
+      log.severe('Caught exception updating focus chain: $e');
+    }
+  }
+}
+
+extension _ViewRefDuplicator on ViewRef {
+  ViewRef duplicate() =>
+      ViewRef(reference: reference.duplicate(ZX.RIGHT_SAME_RIGHTS));
+}
diff --git a/session_shells/ermine/session/meta/workstation_routing.cml b/session_shells/ermine/session/meta/workstation_routing.cml
deleted file mode 100644
index 15d14ed..0000000
--- a/session_shells/ermine/session/meta/workstation_routing.cml
+++ /dev/null
@@ -1,214 +0,0 @@
-{
-    include: [ "syslog/client.shard.cml" ],
-    children: [
-        {
-            name: "workstation_session",
-            url: "fuchsia-pkg://fuchsia.com/workstation_session#meta/workstation_session.cm",
-            startup: "eager",
-        },
-        {
-            name: "scene_manager",
-            url: "fuchsia-pkg://fuchsia.com/scene_manager#meta/scene_manager.cm",
-            startup: "eager",
-        },
-        {
-            name: "element_manager",
-            url: "fuchsia-pkg://fuchsia.com/element_manager#meta/element_manager.cm",
-            environment: "#elements_env",
-        },
-        {
-            name: "dart_jit_runner",
-            url: "fuchsia-pkg://fuchsia.com/dart_jit_runner#meta/dart_jit_runner.cm",
-            startup: "lazy",
-        },
-        {
-            name: "dart_aot_runner",
-            url: "fuchsia-pkg://fuchsia.com/dart_aot_runner#meta/dart_aot_runner.cm",
-            startup: "lazy",
-        },
-        {
-            name: "flutter_jit_runner",
-            url: "fuchsia-pkg://fuchsia.com/flutter_jit_runner#meta/flutter_jit_runner.cm",
-            startup: "lazy",
-        },
-        {
-            name: "flutter_aot_runner",
-            url: "fuchsia-pkg://fuchsia.com/flutter_aot_runner#meta/flutter_aot_runner.cm",
-            startup: "lazy",
-        },
-    ],
-    capabilities: [
-        {
-            protocol: [
-                "fuchsia.element.Manager",
-                "fuchsia.input.injection.InputDeviceRegistry",
-                "fuchsia.ui.accessibility.view.Registry",
-            ],
-        },
-    ],
-    offer: [
-        {
-            protocol: [
-                "fuchsia.logger.LogSink",
-                "fuchsia.ui.composition.Flatland",
-                "fuchsia.ui.composition.FlatlandDisplay",
-                "fuchsia.ui.focus.FocusChainListenerRegistry",
-                "fuchsia.ui.input.ImeService",
-                "fuchsia.ui.input3.Keyboard",
-                "fuchsia.ui.input3.KeyEventInjector",
-                "fuchsia.ui.keyboard.focus.Controller",
-                "fuchsia.ui.pointerinjector.Registry",
-                "fuchsia.ui.scenic.Scenic",
-                "fuchsia.ui.shortcut.Manager",
-                "fuchsia.ui.views.ViewRefInstalled",
-            ],
-            from: "parent",
-            to: [ "#scene_manager" ],
-        },
-        {
-            directory: "dev-input-report",
-            from: "parent",
-            to: [ "#scene_manager" ],
-        },
-        {
-            protocol: [
-                "fuchsia.deprecatedtimezone.Timezone",
-                "fuchsia.feedback.CrashReporter",
-                "fuchsia.identity.account.AccountManager",
-                "fuchsia.intl.PropertyProvider",
-                "fuchsia.logger.LogSink",
-                "fuchsia.posix.socket.Provider",
-                "fuchsia.sys.Launcher",
-                "fuchsia.tracing.provider.Registry",
-                "fuchsia.ui.focus.FocusChainListenerRegistry",
-                "fuchsia.ui.input.ImeService",
-                "fuchsia.ui.keyboard.focus.Controller",
-                "fuchsia.ui.shortcut.Manager",
-                "fuchsia.ui.views.ViewRefInstalled",
-            ],
-            from: "parent",
-            to: [ "#workstation_session" ],
-        },
-        {
-            protocol: [ "fuchsia.session.scene.Manager" ],
-            from: "#scene_manager",
-            to: [ "#workstation_session" ],
-        },
-        {
-            protocol: [
-                "fuchsia.logger.LogSink",
-                "fuchsia.sys.Launcher",
-                "fuchsia.ui.scenic.Scenic",
-            ],
-            from: "parent",
-            to: [ "#element_manager" ],
-        },
-        {
-            protocol: [
-                "fuchsia.deprecatedtimezone.Timezone",
-                "fuchsia.feedback.CrashReporter",
-                "fuchsia.intl.PropertyProvider",
-                "fuchsia.logger.LogSink",
-                "fuchsia.posix.socket.Provider",
-                "fuchsia.tracing.provider.Registry",
-            ],
-            from: "parent",
-            to: [
-                "#dart_aot_runner",
-                "#dart_jit_runner",
-                "#flutter_aot_runner",
-                "#flutter_jit_runner",
-            ],
-        },
-        {
-            protocol: [
-                "fuchsia.fonts.Provider",
-                "fuchsia.sysmem.Allocator",
-                "fuchsia.ui.composition.Flatland",
-                "fuchsia.ui.scenic.Scenic",
-                "fuchsia.vulkan.loader.Loader",
-            ],
-            from: "parent",
-            to: [
-                "#flutter_aot_runner",
-                "#flutter_jit_runner",
-            ],
-        },
-        {
-            directory: "config-data",
-            from: "parent",
-            to: [
-                "#dart_aot_runner",
-                "#dart_jit_runner",
-                "#flutter_aot_runner",
-                "#flutter_jit_runner",
-            ],
-        },
-
-        // The session manually proxies protocols between #element_manager and the shell.
-        // TODO(fxbug.dev/83819) This will be unnecessary once the shell is a V2 component.
-        {
-            protocol: [ "fuchsia.element.Manager" ],
-            from: "#element_manager",
-            to: [ "#workstation_session" ],
-        },
-
-        // The session manually proxies protocols between #element_manager and the shell.
-        // TODO(fxbug.dev/83819) This will be unnecessary once the shell is a V2 component.
-        {
-            protocol: [ "fuchsia.element.GraphicalPresenter" ],
-            from: "#workstation_session",
-            to: [ "#element_manager" ],
-
-            // A circular dependency exists because protocols are routed in both directions between
-            // #workstation_session and #element_manager; a weak reference is uses to "break" this
-            // cycle.  NOTE: this isn't just because the shell is a V1 component; when it becomes a
-            // V2 component, the circular dependency will still exist, except it will be between
-            // #element_manager and the shell, since the session will no longer need to manually
-            // proxy protocols between them.
-            dependency: 'weak',
-        },
-    ],
-    expose: [
-        {
-            protocol: [ "fuchsia.element.Manager" ],
-            from: "#element_manager",
-        },
-        {
-            protocol: [
-                "fuchsia.input.injection.InputDeviceRegistry",
-                "fuchsia.input.keymap.Configuration",
-                "fuchsia.ui.accessibility.view.Registry",
-            ],
-            from: "#scene_manager",
-        },
-        {
-            protocol: "fuchsia.component.Binder",
-            from: "framework",
-        },
-    ],
-    environments: [
-        {
-            name: "elements_env",
-            extends: "realm",
-            runners: [
-                {
-                    runner: "dart_jit_runner",
-                    from: "#dart_jit_runner",
-                },
-                {
-                    runner: "dart_aot_runner",
-                    from: "#dart_aot_runner",
-                },
-                {
-                    runner: "flutter_jit_runner",
-                    from: "#flutter_jit_runner",
-                },
-                {
-                    runner: "flutter_aot_runner",
-                    from: "#flutter_aot_runner",
-                },
-            ],
-        },
-    ],
-}
diff --git a/session_shells/ermine/session/meta/workstation_routing_aot.cml b/session_shells/ermine/session/meta/workstation_routing_aot.cml
new file mode 100644
index 0000000..95ebe38
--- /dev/null
+++ b/session_shells/ermine/session/meta/workstation_routing_aot.cml
@@ -0,0 +1,73 @@
+{
+    include: [ "//src/experiences/session_shells/ermine/session/meta/workstation_routing_common.shard.cml" ],
+    children: [
+        {
+            name: "dart_aot_runner",
+            url: "fuchsia-pkg://fuchsia.com/dart_aot_runner#meta/dart_aot_runner.cm",
+            startup: "lazy",
+        },
+        {
+            name: "flutter_aot_runner",
+            url: "fuchsia-pkg://fuchsia.com/flutter_aot_runner#meta/flutter_aot_runner.cm",
+            startup: "lazy",
+        },
+    ],
+    offer: [
+        {
+            protocol: [
+                "fuchsia.feedback.CrashReporter",
+                "fuchsia.intl.PropertyProvider",
+                "fuchsia.logger.LogSink",
+                "fuchsia.posix.socket.Provider",
+                "fuchsia.tracing.provider.Registry",
+            ],
+            from: "parent",
+            to: [
+                "#dart_aot_runner",
+                "#flutter_aot_runner",
+            ],
+        },
+        {
+            protocol: [
+                "fuchsia.accessibility.semantics.SemanticsManager",
+                "fuchsia.fonts.Provider",
+                "fuchsia.memorypressure.Provider",
+                "fuchsia.settings.Intl",
+                "fuchsia.sysmem.Allocator",
+                "fuchsia.ui.composition.Allocator",
+                "fuchsia.ui.composition.Flatland",
+                "fuchsia.ui.input.ImeService",
+                "fuchsia.ui.input3.Keyboard",
+                "fuchsia.ui.scenic.Scenic",
+                "fuchsia.vulkan.loader.Loader",
+            ],
+            from: "parent",
+            to: [ "#flutter_aot_runner" ],
+        },
+        {
+            directory: "config-data",
+            from: "parent",
+            to: [
+                "#dart_aot_runner",
+                "#flutter_aot_runner",
+                "#workstation_session",
+            ],
+        },
+    ],
+    environments: [
+        {
+            name: "workstation_session_env",
+            extends: "realm",
+            runners: [
+                {
+                    runner: "dart_aot_runner",
+                    from: "#dart_aot_runner",
+                },
+                {
+                    runner: "flutter_aot_runner",
+                    from: "#flutter_aot_runner",
+                },
+            ],
+        },
+    ],
+}
diff --git a/session_shells/ermine/session/meta/workstation_routing_aot_product.cml b/session_shells/ermine/session/meta/workstation_routing_aot_product.cml
new file mode 100644
index 0000000..6b9c059
--- /dev/null
+++ b/session_shells/ermine/session/meta/workstation_routing_aot_product.cml
@@ -0,0 +1,73 @@
+{
+    include: [ "//src/experiences/session_shells/ermine/session/meta/workstation_routing_common.shard.cml" ],
+    children: [
+        {
+            name: "dart_aot_product_runner",
+            url: "fuchsia-pkg://fuchsia.com/dart_aot_product_runner#meta/dart_aot_product_runner.cm",
+            startup: "lazy",
+        },
+        {
+            name: "flutter_aot_product_runner",
+            url: "fuchsia-pkg://fuchsia.com/flutter_aot_product_runner#meta/flutter_aot_product_runner.cm",
+            startup: "lazy",
+        },
+    ],
+    offer: [
+        {
+            protocol: [
+                "fuchsia.feedback.CrashReporter",
+                "fuchsia.intl.PropertyProvider",
+                "fuchsia.logger.LogSink",
+                "fuchsia.posix.socket.Provider",
+                "fuchsia.tracing.provider.Registry",
+            ],
+            from: "parent",
+            to: [
+                "#dart_aot_product_runner",
+                "#flutter_aot_product_runner",
+            ],
+        },
+        {
+            protocol: [
+                "fuchsia.accessibility.semantics.SemanticsManager",
+                "fuchsia.fonts.Provider",
+                "fuchsia.memorypressure.Provider",
+                "fuchsia.settings.Intl",
+                "fuchsia.sysmem.Allocator",
+                "fuchsia.ui.composition.Allocator",
+                "fuchsia.ui.composition.Flatland",
+                "fuchsia.ui.input.ImeService",
+                "fuchsia.ui.input3.Keyboard",
+                "fuchsia.ui.scenic.Scenic",
+                "fuchsia.vulkan.loader.Loader",
+            ],
+            from: "parent",
+            to: [ "#flutter_aot_product_runner" ],
+        },
+        {
+            directory: "config-data",
+            from: "parent",
+            to: [
+                "#dart_aot_product_runner",
+                "#flutter_aot_product_runner",
+                "#workstation_session",
+            ],
+        },
+    ],
+    environments: [
+        {
+            name: "workstation_session_env",
+            extends: "realm",
+            runners: [
+                {
+                    runner: "dart_aot_product_runner",
+                    from: "#dart_aot_product_runner",
+                },
+                {
+                    runner: "flutter_aot_product_runner",
+                    from: "#flutter_aot_product_runner",
+                },
+            ],
+        },
+    ],
+}
diff --git a/session_shells/ermine/session/meta/workstation_routing_common.shard.cml b/session_shells/ermine/session/meta/workstation_routing_common.shard.cml
new file mode 100644
index 0000000..0c974a4
--- /dev/null
+++ b/session_shells/ermine/session/meta/workstation_routing_common.shard.cml
@@ -0,0 +1,107 @@
+{
+    include: [ "syslog/client.shard.cml" ],
+    children: [
+        {
+            name: "workstation_session",
+            url: "fuchsia-pkg://fuchsia.com/workstation_session#meta/workstation_session.cm",
+            startup: "eager",
+            environment: "#workstation_session_env",
+        },
+    ],
+    offer: [
+        {
+            protocol: [
+                "fuchsia.accessibility.semantics.SemanticsManager",
+                "fuchsia.buildinfo.Provider",
+                "fuchsia.feedback.CrashReporter",
+                "fuchsia.fonts.Provider",
+                "fuchsia.hardware.power.statecontrol.Admin",
+                "fuchsia.identity.account.AccountManager",
+                "fuchsia.intl.PropertyProvider",
+                "fuchsia.logger.LogSink",
+                "fuchsia.media.AudioCore",
+                "fuchsia.media.AudioDeviceEnumerator",
+                "fuchsia.media.ProfileProvider",
+                "fuchsia.memory.Monitor",
+                "fuchsia.memorypressure.Provider",
+                "fuchsia.net.interfaces.State",
+                "fuchsia.net.name.Lookup",
+                "fuchsia.posix.socket.Provider",
+                "fuchsia.power.battery.BatteryManager",
+                "fuchsia.process.Launcher",
+                "fuchsia.recovery.FactoryReset",
+                "fuchsia.session.scene.Manager",
+                "fuchsia.settings.Intl",
+                "fuchsia.settings.Keyboard",
+                "fuchsia.settings.Privacy",
+                "fuchsia.ssh.AuthorizedKeys",
+                "fuchsia.sys.Launcher",
+                "fuchsia.sysmem.Allocator",
+                "fuchsia.tracing.provider.Registry",
+                "fuchsia.ui.activity.Provider",
+                "fuchsia.ui.activity.Tracker",
+                "fuchsia.ui.brightness.Control",
+                "fuchsia.ui.composition.Allocator",
+                "fuchsia.ui.composition.Flatland",
+                "fuchsia.ui.focus.FocusChainListenerRegistry",
+                "fuchsia.ui.input.ImeService",
+                "fuchsia.ui.input.PointerCaptureListenerRegistry",
+                "fuchsia.ui.input3.Keyboard",
+                "fuchsia.ui.keyboard.focus.Controller",
+                "fuchsia.ui.scenic.Scenic",
+                "fuchsia.ui.shortcut.Manager",
+                "fuchsia.ui.shortcut.Registry",
+                "fuchsia.ui.views.ViewRefInstalled",
+                "fuchsia.update.channelcontrol.ChannelControl",
+                "fuchsia.update.Manager",
+                "fuchsia.vulkan.loader.Loader",
+                "fuchsia.wlan.policy.ClientProvider",
+            ],
+            from: "parent",
+            to: [ "#workstation_session" ],
+        },
+        {
+            // Protocols used by element_manager
+            protocol: [
+                "fuchsia.logger.LogSink",
+                "fuchsia.media.Audio",
+                "fuchsia.sys.Launcher",
+                "fuchsia.sysmem.Allocator",
+                "fuchsia.tracing.provider.Registry",
+                "fuchsia.ui.composition.Allocator",
+                "fuchsia.ui.composition.Flatland",
+                "fuchsia.ui.input3.Keyboard",
+                "fuchsia.ui.scenic.Scenic",
+            ],
+            from: "parent",
+            to: [ "#workstation_session" ],
+        },
+        {
+            directory: "root-ssl-certificates",
+            from: "parent",
+            to: [ "#workstation_session" ],
+        },
+        {
+            storage: [
+                "cache",
+                "data",
+                "tmp",
+            ],
+            from: "parent",
+            to: "#workstation_session",
+        },
+    ],
+    expose: [
+        {
+            protocol: "fuchsia.component.Binder",
+            from: "framework",
+        },
+        {
+            protocol: [
+                "fuchsia.element.GraphicalPresenter",
+                "fuchsia.element.Manager",
+            ],
+            from: "#workstation_session",
+        },
+    ],
+}
diff --git a/session_shells/ermine/session/meta/workstation_routing_jit.cml b/session_shells/ermine/session/meta/workstation_routing_jit.cml
new file mode 100644
index 0000000..5b4dfa6
--- /dev/null
+++ b/session_shells/ermine/session/meta/workstation_routing_jit.cml
@@ -0,0 +1,73 @@
+{
+    include: [ "//src/experiences/session_shells/ermine/session/meta/workstation_routing_common.shard.cml" ],
+    children: [
+        {
+            name: "dart_jit_runner",
+            url: "fuchsia-pkg://fuchsia.com/dart_jit_runner#meta/dart_jit_runner.cm",
+            startup: "lazy",
+        },
+        {
+            name: "flutter_jit_runner",
+            url: "fuchsia-pkg://fuchsia.com/flutter_jit_runner#meta/flutter_jit_runner.cm",
+            startup: "lazy",
+        },
+    ],
+    offer: [
+        {
+            protocol: [
+                "fuchsia.feedback.CrashReporter",
+                "fuchsia.intl.PropertyProvider",
+                "fuchsia.logger.LogSink",
+                "fuchsia.posix.socket.Provider",
+                "fuchsia.tracing.provider.Registry",
+            ],
+            from: "parent",
+            to: [
+                "#dart_jit_runner",
+                "#flutter_jit_runner",
+            ],
+        },
+        {
+            protocol: [
+                "fuchsia.accessibility.semantics.SemanticsManager",
+                "fuchsia.fonts.Provider",
+                "fuchsia.memorypressure.Provider",
+                "fuchsia.settings.Intl",
+                "fuchsia.sysmem.Allocator",
+                "fuchsia.ui.composition.Allocator",
+                "fuchsia.ui.composition.Flatland",
+                "fuchsia.ui.input.ImeService",
+                "fuchsia.ui.input3.Keyboard",
+                "fuchsia.ui.scenic.Scenic",
+                "fuchsia.vulkan.loader.Loader",
+            ],
+            from: "parent",
+            to: [ "#flutter_jit_runner" ],
+        },
+        {
+            directory: "config-data",
+            from: "parent",
+            to: [
+                "#dart_jit_runner",
+                "#flutter_jit_runner",
+                "#workstation_session",
+            ],
+        },
+    ],
+    environments: [
+        {
+            name: "workstation_session_env",
+            extends: "realm",
+            runners: [
+                {
+                    runner: "dart_jit_runner",
+                    from: "#dart_jit_runner",
+                },
+                {
+                    runner: "flutter_jit_runner",
+                    from: "#flutter_jit_runner",
+                },
+            ],
+        },
+    ],
+}
diff --git a/session_shells/ermine/session/meta/workstation_routing_jit_product.cml b/session_shells/ermine/session/meta/workstation_routing_jit_product.cml
new file mode 100644
index 0000000..270b075
--- /dev/null
+++ b/session_shells/ermine/session/meta/workstation_routing_jit_product.cml
@@ -0,0 +1,73 @@
+{
+    include: [ "//src/experiences/session_shells/ermine/session/meta/workstation_routing_common.shard.cml" ],
+    children: [
+        {
+            name: "dart_jit_product_runner",
+            url: "fuchsia-pkg://fuchsia.com/dart_jit_product_runner#meta/dart_jit_product_runner.cm",
+            startup: "lazy",
+        },
+        {
+            name: "flutter_jit_product_runner",
+            url: "fuchsia-pkg://fuchsia.com/flutter_jit_product_runner#meta/flutter_jit_product_runner.cm",
+            startup: "lazy",
+        },
+    ],
+    offer: [
+        {
+            protocol: [
+                "fuchsia.feedback.CrashReporter",
+                "fuchsia.intl.PropertyProvider",
+                "fuchsia.logger.LogSink",
+                "fuchsia.posix.socket.Provider",
+                "fuchsia.tracing.provider.Registry",
+            ],
+            from: "parent",
+            to: [
+                "#dart_jit_product_runner",
+                "#flutter_jit_product_runner",
+            ],
+        },
+        {
+            protocol: [
+                "fuchsia.accessibility.semantics.SemanticsManager",
+                "fuchsia.fonts.Provider",
+                "fuchsia.memorypressure.Provider",
+                "fuchsia.settings.Intl",
+                "fuchsia.sysmem.Allocator",
+                "fuchsia.ui.composition.Allocator",
+                "fuchsia.ui.composition.Flatland",
+                "fuchsia.ui.input.ImeService",
+                "fuchsia.ui.input3.Keyboard",
+                "fuchsia.ui.scenic.Scenic",
+                "fuchsia.vulkan.loader.Loader",
+            ],
+            from: "parent",
+            to: [ "#flutter_jit_product_runner" ],
+        },
+        {
+            directory: "config-data",
+            from: "parent",
+            to: [
+                "#dart_jit_product_runner",
+                "#flutter_jit_product_runner",
+                "#workstation_session",
+            ],
+        },
+    ],
+    environments: [
+        {
+            name: "workstation_session_env",
+            extends: "realm",
+            runners: [
+                {
+                    runner: "dart_jit_product_runner",
+                    from: "#dart_jit_product_runner",
+                },
+                {
+                    runner: "flutter_jit_product_runner",
+                    from: "#flutter_jit_product_runner",
+                },
+            ],
+        },
+    ],
+}
diff --git a/session_shells/ermine/session/meta/workstation_session.cml b/session_shells/ermine/session/meta/workstation_session.cml
index b47d446..bc4bb3c 100644
--- a/session_shells/ermine/session/meta/workstation_session.cml
+++ b/session_shells/ermine/session/meta/workstation_session.cml
@@ -4,49 +4,121 @@
         "syslog/client.shard.cml",
     ],
     program: {
-        runner: "elf",
-        binary: "bin/workstation_session",
+        data: "data/workstation_session",
     },
-    capabilities: [
+    children: [
         {
-            protocol: [ "fuchsia.element.GraphicalPresenter" ],
-        },
-        {
-            directory: "account_dir",
-            rights: [ "rw*" ],
-            path: "/account_data",
-        },
-        {
-            storage: "account",
-            from: "self",
-            backing_dir: "account_dir",
-            storage_id: "static_instance_id_or_moniker",
+            name: "login_shell",
+            url: "fuchsia-pkg://fuchsia.com/ermine#meta/login.cm",
+            startup: "eager",
         },
     ],
     use: [
         {
+            protocol: "fuchsia.component.Realm",
+            from: "framework",
+        },
+        {
             protocol: [
-                "fuchsia.deprecatedtimezone.Timezone",
-                "fuchsia.element.Manager",
-                "fuchsia.feedback.CrashReporter",
-                "fuchsia.identity.account.AccountManager",
-                "fuchsia.intl.PropertyProvider",
-                "fuchsia.posix.socket.Provider",
                 "fuchsia.session.scene.Manager",
-                "fuchsia.sys.Launcher",
-                "fuchsia.tracing.provider.Registry",
                 "fuchsia.ui.focus.FocusChainListenerRegistry",
-                "fuchsia.ui.input.ImeService",
                 "fuchsia.ui.keyboard.focus.Controller",
                 "fuchsia.ui.shortcut.Manager",
                 "fuchsia.ui.views.ViewRefInstalled",
             ],
         },
+        {
+            directory: "config-data",
+            from: "parent",
+            rights: [ "r*" ],
+            path: "/config/data",
+        },
+    ],
+    offer: [
+        {
+            protocol: [
+                "fuchsia.accessibility.semantics.SemanticsManager",
+                "fuchsia.buildinfo.Provider",
+                "fuchsia.element.Manager",
+                "fuchsia.feedback.CrashReporter",
+                "fuchsia.fonts.Provider",
+                "fuchsia.hardware.power.statecontrol.Admin",
+                "fuchsia.identity.account.AccountManager",
+                "fuchsia.intl.PropertyProvider",
+                "fuchsia.logger.LogSink",
+                "fuchsia.media.Audio",
+                "fuchsia.media.AudioCore",
+                "fuchsia.media.AudioDeviceEnumerator",
+                "fuchsia.media.ProfileProvider",
+                "fuchsia.memory.Monitor",
+                "fuchsia.memorypressure.Provider",
+                "fuchsia.net.interfaces.State",
+                "fuchsia.net.name.Lookup",
+                "fuchsia.posix.socket.Provider",
+                "fuchsia.power.battery.BatteryManager",
+                "fuchsia.process.Launcher",
+                "fuchsia.recovery.FactoryReset",
+                "fuchsia.settings.Intl",
+                "fuchsia.settings.Keyboard",
+                "fuchsia.settings.Privacy",
+                "fuchsia.ssh.AuthorizedKeys",
+                "fuchsia.sys.Launcher",
+                "fuchsia.sysmem.Allocator",
+                "fuchsia.tracing.provider.Registry",
+                "fuchsia.ui.activity.Provider",
+                "fuchsia.ui.activity.Tracker",
+                "fuchsia.ui.brightness.Control",
+                "fuchsia.ui.composition.Allocator",
+                "fuchsia.ui.composition.Flatland",
+                "fuchsia.ui.focus.FocusChainListenerRegistry",
+                "fuchsia.ui.input.ImeService",
+                "fuchsia.ui.input.PointerCaptureListenerRegistry",
+                "fuchsia.ui.input3.Keyboard",
+                "fuchsia.ui.keyboard.focus.Controller",
+                "fuchsia.ui.scenic.Scenic",
+                "fuchsia.ui.shortcut.Registry",
+                "fuchsia.ui.views.ViewRefInstalled",
+                "fuchsia.update.channelcontrol.ChannelControl",
+                "fuchsia.update.Manager",
+                "fuchsia.vulkan.loader.Loader",
+                "fuchsia.wlan.policy.ClientProvider",
+            ],
+            from: "parent",
+            to: [ "#login_shell" ],
+        },
+        {
+            directory: "config-data",
+            from: "parent",
+            to: "#login_shell",
+        },
+        {
+            directory: "root-ssl-certificates",
+            from: "parent",
+            to: [ "#login_shell" ],
+        },
+        {
+            storage: [
+                "cache",
+                "tmp",
+            ],
+            from: "parent",
+            to: "#login_shell",
+        },
+
+        // Note: The "data" storage capability used to store
+        // device data is not passed to login_shell, components
+        // inside the session should use the "account" storage
+        // capability intended for storaging account data. The
+        // account storage capability is encrypted using the
+        // account's authentication factors.
     ],
     expose: [
         {
-            protocol: [ "fuchsia.element.GraphicalPresenter" ],
-            from: "self",
+            protocol: [
+                "fuchsia.element.GraphicalPresenter",
+                "fuchsia.element.Manager",
+            ],
+            from: "#login_shell",
         },
     ],
 }
diff --git a/session_shells/ermine/session/meta/workstation_session_bin_test.cml b/session_shells/ermine/session/meta/workstation_session_bin_test.cml
deleted file mode 100644
index d738945..0000000
--- a/session_shells/ermine/session/meta/workstation_session_bin_test.cml
+++ /dev/null
@@ -1,25 +0,0 @@
-{
-    include: [
-        "//sdk/lib/inspect/client.shard.cml",
-        "syslog/client.shard.cml",
-    ],
-    program: {
-        runner: "rust_test_runner",
-        binary: "bin/workstation_session_bin_test",
-    },
-    capabilities: [
-        { protocol: "fuchsia.test.Suite" },
-    ],
-    use: [
-        {
-            protocol: "fuchsia.sys2.Realm",
-            from: "framework",
-        },
-    ],
-    expose: [
-        {
-            protocol: "fuchsia.test.Suite",
-            from: "self",
-        },
-    ],
-}
diff --git a/lib/quickui/pubspec.yaml b/session_shells/ermine/session/pubspec.yaml
similarity index 86%
rename from lib/quickui/pubspec.yaml
rename to session_shells/ermine/session/pubspec.yaml
index dd36530..74d24fc 100644
--- a/lib/quickui/pubspec.yaml
+++ b/session_shells/ermine/session/pubspec.yaml
@@ -2,4 +2,4 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-name: quickui
\ No newline at end of file
+name: workstation_session
\ No newline at end of file
diff --git a/session_shells/ermine/session/session_config.json b/session_shells/ermine/session/session_config.json
deleted file mode 100644
index bd854ba..0000000
--- a/session_shells/ermine/session/session_config.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
-    "session_url": "fuchsia-pkg://fuchsia.com/workstation_routing#meta/workstation_routing.cm"
-}
diff --git a/session_shells/ermine/session/session_config.json5 b/session_shells/ermine/session/session_config.json5
new file mode 100644
index 0000000..9403d69
--- /dev/null
+++ b/session_shells/ermine/session/session_config.json5
@@ -0,0 +1,3 @@
+{
+    session_url: "fuchsia-pkg://fuchsia.com/workstation_session#meta/workstation_routing.cm"
+}
diff --git a/session_shells/ermine/session/src/main.rs b/session_shells/ermine/session/src/main.rs
deleted file mode 100644
index b8f00ca..0000000
--- a/session_shells/ermine/session/src/main.rs
+++ /dev/null
@@ -1,297 +0,0 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// This declaration is required to support the `select!`.
-#![recursion_limit = "256"]
-
-use {
-    anyhow::{anyhow, Context as _, Error},
-    fidl::endpoints::{create_endpoints, ClientEnd, DiscoverableProtocolMarker, Proxy},
-    fidl_fuchsia_element::{
-        GraphicalPresenterMarker, GraphicalPresenterProxy, GraphicalPresenterRequest,
-        GraphicalPresenterRequestStream, ManagerMarker as ElementManagerMarker,
-        ManagerProxy as ElementManagerProxy, ManagerRequest as ElementManagerRequest,
-        ManagerRequestStream as ElementManagerRequestStream,
-    },
-    fidl_fuchsia_identity_account::{AccountManagerMarker, AccountMetadata, AccountProxy},
-    fidl_fuchsia_io::DirectoryProxy,
-    fidl_fuchsia_session_scene::ManagerMarker as SceneManagerMarker,
-    fidl_fuchsia_sys::LauncherMarker,
-    fidl_fuchsia_ui_app::ViewProviderMarker,
-    fidl_fuchsia_ui_views as ui_views,
-    fidl_fuchsia_ui_views::ViewRefInstalledMarker,
-    fuchsia_async as fasync,
-    fuchsia_component::{
-        client::{connect_to_protocol, launch_with_options, App, LaunchOptions},
-        server::ServiceFs,
-    },
-    fuchsia_zircon as zx,
-    futures::{try_join, StreamExt, TryStreamExt},
-    log::{error, info, warn},
-    std::fs,
-    std::rc::Rc,
-    std::sync::{Arc, Weak},
-};
-
-enum ExposedServices {
-    ElementManager(ElementManagerRequestStream),
-    GraphicalPresenter(GraphicalPresenterRequestStream),
-}
-
-/// The maximum number of open requests to this component.
-///
-/// Currently we have this value set low because the only service we are serving
-/// is the ElementManager service and we don't expect many connections to it at
-/// any given time.
-const NUM_CONCURRENT_REQUESTS: usize = 5;
-
-/// A hardcoded password to send on the AccountManager interface.
-const EMPTY_PASSWORD: &str = "";
-
-/// A hardcoded name to set on all accounts we create via AccountManager.
-const ACCOUNT_NAME: &str = "created_by_session";
-
-async fn launch_ermine() -> Result<(App, zx::Channel), Error> {
-    let launcher = connect_to_protocol::<LauncherMarker>()?;
-
-    let (client_chan, server_chan) = zx::Channel::create().unwrap();
-
-    let mut launch_options = LaunchOptions::new();
-    launch_options.set_additional_services(
-        vec![ElementManagerMarker::PROTOCOL_NAME.to_string()],
-        client_chan,
-    );
-
-    // Check if shell is overridden. Otherwise start ermine's login shell.
-    let shell_url = match fs::read_to_string("/config/data/shell") {
-        Ok(url) => url,
-        Err(_) => "fuchsia-pkg://fuchsia.com/ermine#meta/login.cmx".to_string(),
-    };
-
-    let app = launch_with_options(&launcher, shell_url, None, launch_options)?;
-
-    Ok((app, server_chan))
-}
-
-async fn expose_services(
-    graphical_presenter: GraphicalPresenterProxy,
-    element_manager: ElementManagerProxy,
-    ermine_services_server_end: zx::Channel,
-    account_dir: Option<DirectoryProxy>,
-) -> Result<(), Error> {
-    let mut fs = ServiceFs::new();
-
-    // Add services for component outgoing directory.
-    fs.dir("svc").add_fidl_service(ExposedServices::GraphicalPresenter);
-    fs.take_and_serve_directory_handle()?;
-
-    // Add the account directory if we were able to acquire it.
-    if let Some(proxy) = account_dir {
-        fs.add_remote("account_data", proxy);
-    }
-
-    // Add services served to Ermine over `ermine_services_server_end`.
-    fs.add_fidl_service_at(ElementManagerMarker::PROTOCOL_NAME, ExposedServices::ElementManager);
-    fs.serve_connection(ermine_services_server_end).unwrap();
-
-    let graphical_presenter = Rc::new(graphical_presenter);
-    let element_manager = Rc::new(element_manager);
-
-    fs.for_each_concurrent(NUM_CONCURRENT_REQUESTS, |service_request: ExposedServices| {
-        // It's a bit unforunate to clone both of these for each service request, since each service
-        // requires only one of the two.  However, as long as we have an "async move" block, we must
-        // clone the refs before they are moved into it.
-        let graphical_presenter = graphical_presenter.clone();
-        let element_manager = element_manager.clone();
-
-        async move {
-            match service_request {
-                ExposedServices::ElementManager(request_stream) => {
-                    run_proxy_element_manager_service(element_manager, request_stream)
-                        .await
-                        .unwrap_or_else(|e| error!("Failure in element manager proxy: {}", e));
-                }
-                ExposedServices::GraphicalPresenter(request_stream) => {
-                    run_proxy_graphical_presenter_service(graphical_presenter, request_stream)
-                        .await
-                        .unwrap_or_else(|e| error!("Failure in graphical presenter proxy: {}", e));
-                }
-            }
-        }
-    })
-    .await;
-
-    Ok(())
-}
-
-async fn run_proxy_element_manager_service(
-    element_manager: Rc<ElementManagerProxy>,
-    mut request_stream: ElementManagerRequestStream,
-) -> Result<(), Error> {
-    while let Some(request) =
-        request_stream.try_next().await.context("Failed to obtain next request from stream")?
-    {
-        match request {
-            ElementManagerRequest::ProposeElement { spec, controller, responder } => {
-                // TODO(fxbug.dev/47079): handle error
-                let mut result = element_manager
-                    .propose_element(spec, controller)
-                    .await
-                    .context("Failed to forward proxied request")?;
-                let _ = responder.send(&mut result);
-            }
-        }
-    }
-    Ok(())
-}
-
-async fn run_proxy_graphical_presenter_service(
-    graphical_presenter: Rc<GraphicalPresenterProxy>,
-    mut request_stream: GraphicalPresenterRequestStream,
-) -> Result<(), Error> {
-    while let Some(request) =
-        request_stream.try_next().await.context("Failed to obtain next request from stream")?
-    {
-        match request {
-            GraphicalPresenterRequest::PresentView {
-                view_spec,
-                annotation_controller,
-                view_controller_request,
-                responder,
-            } => {
-                // TODO(fxbug.dev/47079): handle error
-                let mut result = graphical_presenter
-                    .present_view(view_spec, annotation_controller, view_controller_request)
-                    .await
-                    .context("Failed to forward proxied request")?;
-                let _ = responder.send(&mut result);
-            }
-        }
-    }
-    Ok(())
-}
-
-async fn set_view_focus(
-    weak_focuser: Weak<fidl_fuchsia_session_scene::ManagerProxy>,
-    mut view_ref: ui_views::ViewRef,
-) -> Result<(), Error> {
-    // [ViewRef]'s are one-shot use only. Duplicate it for use in request_focus below.
-    let mut viewref_dup = fuchsia_scenic::duplicate_view_ref(&view_ref)?;
-
-    // Wait for the view_ref to signal its ready to be focused.
-    let view_ref_installed = connect_to_protocol::<ViewRefInstalledMarker>()
-        .context("Could not connect to ViewRefInstalledMarker")?;
-    let watch_result = view_ref_installed.watch(&mut view_ref).await;
-    match watch_result {
-        // Handle fidl::Errors.
-        Err(e) => Err(anyhow::format_err!("Failed with err: {}", e)),
-        // Handle ui_views::ViewRefInstalledError.
-        Ok(Err(value)) => Err(anyhow::format_err!("Failed with err: {:?}", value)),
-        Ok(_) => {
-            // Now set focus on the view_ref.
-            if let Some(focuser) = weak_focuser.upgrade() {
-                let focus_result = focuser.request_focus(&mut viewref_dup).await?;
-                match focus_result {
-                    Ok(()) => Ok(()),
-                    Err(e) => Err(anyhow::format_err!("Failed with err: {:?}", e)),
-                }
-            } else {
-                Err(anyhow::format_err!("Failed to acquire Focuser"))
-            }
-        }
-    }
-}
-
-/// Use the AccountManager API (with the supplied password) to either get the only existing account
-/// or create a new account then acquire a data directory for that account.
-async fn get_account_directory(password: &str) -> Result<DirectoryProxy, Error> {
-    let account_manager = Arc::new(connect_to_protocol::<AccountManagerMarker>().unwrap());
-    info!("Connected to AccountManager");
-
-    let account_ids = account_manager.get_account_ids().await?;
-    let maybe_account_id = match account_ids.len() {
-        0 => None,
-        1 => Some(account_ids[0]),
-        count => {
-            return Err(anyhow!("Multiple ({}) accounts found, cannot get data directory", count));
-        }
-    };
-
-    let (account_client_end, account_server_end) = create_endpoints()?;
-    let account_metadata =
-        AccountMetadata { name: Some(ACCOUNT_NAME.to_string()), ..AccountMetadata::EMPTY };
-
-    match maybe_account_id {
-        None => {
-            info!("Creating a new account through AccountManager");
-            account_manager
-                .deprecated_provision_new_account(password, account_metadata, account_server_end)
-                .await?
-                .map_err(|err| anyhow!("Error provisioning new account: {:?}", err))?;
-        }
-        Some(account_id) => {
-            info!("Getting existing account with ID {}", account_id);
-            account_manager
-                .deprecated_get_account(account_id, password, account_server_end)
-                .await?
-                .map_err(|err| anyhow!("Error getting account: {:?}", err))?;
-        }
-    }
-
-    let account: AccountProxy = account_client_end.into_proxy()?;
-    let (directory_client_end, directory_server_end) = create_endpoints()?;
-    info!("Getting directory on account");
-    account
-        .get_data_directory(directory_server_end)
-        .await?
-        .map_err(|err| anyhow!("Error getting data directory: {:?}", err))?;
-    Ok(directory_client_end.into_proxy()?)
-}
-
-#[fasync::run_singlethreaded]
-async fn main() -> Result<(), Error> {
-    fuchsia_syslog::init_with_tags(&["workstation_session"]).expect("Failed to initialize logger.");
-
-    let (app, ermine_services_server_end) = launch_ermine().await?;
-    let view_provider = app.connect_to_protocol::<ViewProviderMarker>()?;
-
-    // Attempt to retrieve a data directory for the account from AccountManager. If this fails
-    // just continue without the directory.
-    let maybe_account_dir = match get_account_directory(EMPTY_PASSWORD).await {
-        Ok(dir) => {
-            info!("Successfully acquired an account directory");
-            Some(dir)
-        }
-        Err(err) => {
-            warn!("Error getting account directory: {:?}", err);
-            None
-        }
-    };
-
-    let scene_manager = Arc::new(connect_to_protocol::<SceneManagerMarker>().unwrap());
-
-    let shell_view_provider: ClientEnd<ViewProviderMarker> = view_provider
-        .into_channel()
-        .expect("no other users of the wrapped channel")
-        .into_zx_channel()
-        .into();
-    let view_ref = scene_manager.set_root_view(shell_view_provider.into()).await.unwrap();
-
-    let set_focus_fut = set_view_focus(Arc::downgrade(&scene_manager), view_ref);
-    let focus_fut = input_pipeline::focus_listening::handle_focus_changes();
-
-    let graphical_presenter = app.connect_to_protocol::<GraphicalPresenterMarker>()?;
-    let element_manager = connect_to_protocol::<ElementManagerMarker>()?;
-    let services_fut = expose_services(
-        graphical_presenter,
-        element_manager,
-        ermine_services_server_end,
-        maybe_account_dir,
-    );
-
-    //TODO(fxbug.dev/47080) monitor the futures to see if they complete in an error.
-    let _ = try_join!(focus_fut, set_focus_fut, services_fut);
-
-    Ok(())
-}
diff --git a/session_shells/ermine/settings/BUILD.gn b/session_shells/ermine/settings/BUILD.gn
deleted file mode 100644
index 1ecfdc8..0000000
--- a/session_shells/ermine/settings/BUILD.gn
+++ /dev/null
@@ -1,73 +0,0 @@
-# Copyright 2019 The Fuchsia Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import("//build/dart/dart_library.gni")
-import("//build/flutter/test.gni")
-
-dart_library("settings") {
-  package_name = "settings"
-  null_safe = true
-
-  sources = [
-    "settings.dart",
-    "src/battery.dart",
-    "src/brightness.dart",
-    "src/channel.dart",
-    "src/datetime.dart",
-    "src/memory.dart",
-    "src/system_information.dart",
-    "src/timezone.dart",
-    "src/volume.dart",
-  ]
-
-  deps = [
-    "//sdk/dart/fuchsia_logger",
-    "//sdk/dart/fuchsia_services",
-    "//sdk/fidl/fuchsia.intl",
-    "//sdk/fidl/fuchsia.media",
-    "//sdk/fidl/fuchsia.memory",
-    "//sdk/fidl/fuchsia.power",
-    "//sdk/fidl/fuchsia.session",
-    "//sdk/fidl/fuchsia.settings",
-    "//sdk/fidl/fuchsia.ui.brightness",
-    "//sdk/fidl/fuchsia.ui.remotewidgets",
-    "//sdk/fidl/fuchsia.update.channelcontrol",
-    "//src/experiences/lib/quickui",
-    "//src/experiences/session_shells/ermine/internationalization",
-    "//third_party/dart-pkg/git/flutter/packages/flutter",
-    "//third_party/dart-pkg/pub/async",
-    "//third_party/dart-pkg/pub/intl",
-  ]
-}
-
-flutter_test("ermine_settings_unittests") {
-  null_safe = true
-  sources = [
-    "battery_test.dart",
-    "brightness_test.dart",
-    "channel_test.dart",
-    "datetime_test.dart",
-    "memory_test.dart",
-    "system_information_test.dart",
-    "timezone_test.dart",
-    "volume_test.dart",
-  ]
-
-  deps = [
-    ":settings",
-    "//sdk/dart/fidl",
-    "//sdk/fidl/fuchsia.intl",
-    "//sdk/fidl/fuchsia.media",
-    "//sdk/fidl/fuchsia.memory",
-    "//sdk/fidl/fuchsia.power",
-    "//sdk/fidl/fuchsia.settings",
-    "//sdk/fidl/fuchsia.ui.brightness",
-    "//sdk/fidl/fuchsia.ui.remotewidgets",
-    "//sdk/fidl/fuchsia.update.channelcontrol",
-    "//src/experiences/lib/quickui",
-    "//src/experiences/session_shells/ermine/internationalization",
-    "//third_party/dart-pkg/git/flutter/packages/flutter_test",
-    "//third_party/dart-pkg/pub/mockito",
-  ]
-}
diff --git a/session_shells/ermine/settings/lib/settings.dart b/session_shells/ermine/settings/lib/settings.dart
deleted file mode 100644
index 38a7ccf..0000000
--- a/session_shells/ermine/settings/lib/settings.dart
+++ /dev/null
@@ -1,12 +0,0 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-export 'src/battery.dart';
-export 'src/brightness.dart';
-export 'src/channel.dart';
-export 'src/datetime.dart';
-export 'src/memory.dart';
-export 'src/system_information.dart';
-export 'src/timezone.dart';
-export 'src/volume.dart';
diff --git a/session_shells/ermine/settings/lib/src/battery.dart b/session_shells/ermine/settings/lib/src/battery.dart
deleted file mode 100644
index 7defda5..0000000
--- a/session_shells/ermine/settings/lib/src/battery.dart
+++ /dev/null
@@ -1,154 +0,0 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'dart:async';
-
-import 'package:fidl_fuchsia_power/fidl_async.dart';
-import 'package:fidl_fuchsia_ui_remotewidgets/fidl_async.dart';
-import 'package:flutter/material.dart';
-import 'package:fuchsia_services/services.dart' show Incoming;
-import 'package:internationalization/strings.dart';
-import 'package:quickui/quickui.dart';
-
-// ignore_for_file: prefer_constructors_over_static_methods
-
-/// Defines a [UiSpec] for visualizing battery.
-class Battery extends UiSpec {
-  // Localized strings.
-  static String get _title => Strings.battery;
-
-  late BatteryModel model;
-
-  Battery({
-    required BatteryManagerProxy monitor,
-    BatteryInfoWatcherBinding? binding,
-  }) {
-    model = BatteryModel(
-      monitor: monitor,
-      binding: binding,
-      onChange: _onChange,
-    );
-  }
-
-  factory Battery.withSvcPath() {
-    final batteryManager = BatteryManagerProxy();
-    Incoming.fromSvcPath().connectToService(batteryManager);
-    return Battery(monitor: batteryManager);
-  }
-
-  void _onChange() {
-    spec = _specForBattery(model.battery, model.charging);
-  }
-
-  @override
-  void update(Value value) async {}
-
-  @override
-  void dispose() {
-    model.dispose();
-  }
-
-  static Spec _specForBattery(double value, bool charging) {
-    if (value.isNaN) {
-      // Send nullSpec to hide battery settings.
-      return UiSpec.nullSpec;
-    }
-    final batteryText = '${value.toStringAsFixed(0)}%';
-    if (value == 100) {
-      return Spec(title: _title, groups: [
-        Group(
-          title: _title,
-          icon: IconValue(codePoint: Icons.battery_full.codePoint),
-          values: [Value.withText(TextValue(text: batteryText))],
-        ),
-      ]);
-    } else if (charging) {
-      return Spec(title: _title, groups: [
-        Group(
-          title: _title,
-          icon: IconValue(codePoint: Icons.battery_charging_full.codePoint),
-          values: [Value.withText(TextValue(text: batteryText))],
-        ),
-      ]);
-    } else if (value <= 10) {
-      return Spec(title: _title, groups: [
-        Group(
-          title: _title,
-          icon: IconValue(codePoint: Icons.battery_alert.codePoint),
-          values: [Value.withText(TextValue(text: batteryText))],
-        ),
-      ]);
-    } else {
-      return Spec(title: _title, groups: [
-        Group(
-          title: _title,
-          icon: IconValue(codePoint: Icons.battery_std.codePoint),
-          values: [Value.withText(TextValue(text: batteryText))],
-        ),
-      ]);
-    }
-  }
-}
-
-class BatteryModel {
-  final VoidCallback onChange;
-  final BatteryInfoWatcherBinding _binding;
-  final BatteryManagerProxy _monitor;
-
-  late double _battery;
-  late bool charging;
-
-  BatteryModel({
-    required this.onChange,
-    required BatteryManagerProxy monitor,
-    BatteryInfoWatcherBinding? binding,
-  })  : _binding = binding ?? BatteryInfoWatcherBinding(),
-        _monitor = monitor {
-    // Note that watcher will receive callback immediately with
-    // current battery info, so no need to make additional calls
-    // to get initial state.
-    _monitor.watch(_binding.wrap(_BatteryInfoWatcherImpl(this)));
-  }
-
-  void dispose() {
-    _monitor.ctrl.close();
-    _binding.close();
-  }
-
-  double get battery => _battery;
-  set battery(double value) {
-    _battery = value;
-    onChange();
-  }
-
-  void _updateBattery(BatteryInfo info) {
-    // BatteryStatus.ok indicates that the battery is present and
-    // in a known state (so we can show battery info).
-    // Alternate states include:
-    //     BatteryStatus.unknown - not yet initialized
-    //                             (waiting for information from the system)
-    //     BatteryStatus.notAvailable = battery present, but possibly disabled
-    //     BatteryStatus.notPresent = batteries not included
-    if (info.status == BatteryStatus.ok) {
-      final chargeStatus = info.chargeStatus;
-      charging = chargeStatus == ChargeStatus.charging;
-      battery = info.levelPercent!;
-    } else if (info.status == BatteryStatus.notPresent) {
-      // upon receiving report of status 'notPresent' it is safe to close the
-      // connection and stop listening for battery status updates.
-      _monitor.ctrl.close();
-      _binding.close();
-    }
-  }
-}
-
-class _BatteryInfoWatcherImpl extends BatteryInfoWatcher {
-  final BatteryModel batteryModel;
-  _BatteryInfoWatcherImpl(this.batteryModel);
-
-  @override
-  Future<void> onChangeBatteryInfo(BatteryInfo info) async {
-    batteryModel._updateBattery(info);
-  }
-}
diff --git a/session_shells/ermine/settings/lib/src/brightness.dart b/session_shells/ermine/settings/lib/src/brightness.dart
deleted file mode 100644
index 4a03668..0000000
--- a/session_shells/ermine/settings/lib/src/brightness.dart
+++ /dev/null
@@ -1,151 +0,0 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'dart:async';
-
-import 'package:fidl_fuchsia_ui_brightness/fidl_async.dart';
-import 'package:fidl_fuchsia_ui_remotewidgets/fidl_async.dart';
-import 'package:flutter/material.dart';
-import 'package:fuchsia_services/services.dart' show Incoming;
-import 'package:internationalization/strings.dart';
-import 'package:quickui/quickui.dart';
-
-// ignore_for_file: prefer_constructors_over_static_methods
-
-/// Defines a [UiSpec] for controlling screen brightness.
-///
-/// * If the system does not have hardware for brightness control, no [Spec] is
-///   generated.
-/// * If brightness is set to AUTO, show slider followed by AUTO label:
-///         ----------------------
-///   {A}   |////////////        |       AUTO
-///         ----------------------
-/// * If brighness is MANUAL, show slider followed by AUTO button:
-///         ----------------------
-///   {b}   |////////////        |  {B} {AUTO}
-///         ----------------------
-/// * {A} - Auto icon, {b} Brightness min icon, {B} Brightness max icon.
-class Brightness extends UiSpec {
-  static const progressAction = 1;
-  static const autoAction = 2;
-
-  // Localized strings.
-  static String get _title => Strings.brightness;
-  static String get _auto => Strings.auto;
-
-  late _BrightnessModel _model;
-
-  Brightness(ControlProxy control) {
-    _model = _BrightnessModel(control: control, onChange: _onChange);
-  }
-
-  factory Brightness.withSvcPath() {
-    final control = ControlProxy();
-    Incoming.fromSvcPath().connectToService(control);
-    return Brightness(control);
-  }
-
-  void _onChange() {
-    spec = _model.auto
-        ? _specForAutoBrightness(_model.brightness)
-        : _specForManualBrightness(_model.brightness);
-  }
-
-  @override
-  void update(Value value) async {
-    if (value.$tag == ValueTag.progress &&
-        value.progress!.action == progressAction) {
-      _model.brightness = value.progress!.value;
-    } else if (value.$tag == ValueTag.button &&
-        value.button!.action == autoAction) {
-      _model.auto = true;
-    }
-  }
-
-  @override
-  void dispose() {
-    _model.dispose();
-  }
-
-  static Spec _specForAutoBrightness(double value) {
-    return Spec(title: _title, groups: [
-      Group(
-        title: _title,
-        icon: IconValue(codePoint: Icons.brightness_auto.codePoint),
-        values: [
-          Value.withProgress(
-              ProgressValue(value: value, action: progressAction)),
-          Value.withText(TextValue(text: _auto)),
-        ],
-      ),
-    ]);
-  }
-
-  static Spec _specForManualBrightness(double value) {
-    return Spec(title: _title, groups: [
-      Group(
-          title: _title,
-          icon: IconValue(codePoint: Icons.brightness_5.codePoint),
-          values: [
-            Value.withIcon(
-                IconValue(codePoint: Icons.brightness_low.codePoint)),
-            Value.withProgress(
-                ProgressValue(value: value, action: progressAction)),
-            Value.withIcon(
-                IconValue(codePoint: Icons.brightness_high.codePoint)),
-            Value.withButton(ButtonValue(label: _auto, action: autoAction)),
-          ]),
-    ]);
-  }
-}
-
-class _BrightnessModel {
-  final ControlProxy control;
-  final VoidCallback onChange;
-
-  late bool _auto;
-  late bool _enabled = false;
-  late double _brightness;
-  late StreamSubscription _brightnessSubscription;
-
-  _BrightnessModel({required this.control, required this.onChange}) {
-    control.watchAutoBrightness().then((auto) {
-      _enabled = true;
-      _auto = auto;
-      _listen();
-    });
-  }
-
-  void dispose() {
-    control.ctrl.close();
-    _brightnessSubscription.cancel();
-  }
-
-  bool get enabled => _enabled;
-
-  bool get auto => _auto;
-  set auto(bool value) {
-    _auto = value;
-    control.setAutoBrightness();
-    onChange();
-  }
-
-  double get brightness => _brightness;
-  set brightness(double value) {
-    _brightness = value;
-    _auto = false;
-    control.setManualBrightness(value);
-    onChange();
-  }
-
-  void _listen() {
-    _brightnessSubscription =
-        control.watchCurrentBrightness().asStream().listen((brightness) {
-      _brightnessSubscription.cancel();
-      _brightness = brightness;
-      onChange();
-      _listen();
-    });
-  }
-}
diff --git a/session_shells/ermine/settings/lib/src/channel.dart b/session_shells/ermine/settings/lib/src/channel.dart
deleted file mode 100644
index 0f86b83..0000000
--- a/session_shells/ermine/settings/lib/src/channel.dart
+++ /dev/null
@@ -1,142 +0,0 @@
-// Copyright 2021 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'package:fidl_fuchsia_ui_remotewidgets/fidl_async.dart';
-import 'package:fidl_fuchsia_update_channelcontrol/fidl_async.dart';
-import 'package:flutter/material.dart';
-import 'package:fuchsia_services/services.dart' show Incoming;
-import 'package:internationalization/strings.dart';
-import 'package:quickui/quickui.dart';
-
-/// Defines a [UiSpec] for displaying channel.
-class Channel extends UiSpec {
-  // Localized strings.
-  static String get _title => Strings.channel;
-
-  // Icon for channel title.
-  static IconValue get _icon =>
-      IconValue(codePoint: Icons.cloud_outlined.codePoint);
-
-  // Action to change channel.
-  static int changeAction = QuickAction.details.$value;
-
-  late _ChannelModel model;
-
-  Channel(ChannelControlProxy control) {
-    model = _ChannelModel(control: control, onChange: _onChange);
-  }
-
-  factory Channel.withSvcPath() {
-    final control = ChannelControlProxy();
-    Incoming.fromSvcPath().connectToService(control);
-    return Channel(control);
-  }
-
-  void _onChange() async {
-    spec = await _specForChannel(model);
-  }
-
-  @override
-  void update(Value value) async {
-    if (value.$tag == ValueTag.button &&
-        value.button!.action == QuickAction.cancel.$value) {
-      spec = await _specForChannel(model);
-    } else if (value.$tag == ValueTag.text && value.text!.action > 0) {
-      if (value.text!.action == changeAction) {
-        spec = await _specForChannel(model, changeAction);
-      } else {
-        final index = value.text!.action ^ QuickAction.submit.$value;
-        model.channel = model.channels[index];
-        spec = await _specForChannel(model);
-      }
-    }
-  }
-
-  @override
-  void dispose() {
-    model.dispose();
-  }
-
-  Future<Spec> _specForChannel(_ChannelModel model, [int action = 0]) async {
-    if (action == 0 || action & QuickAction.cancel.$value > 0) {
-      return Spec(title: _title, groups: [
-        Group(title: _title, icon: _icon, values: [
-          Value.withText(TextValue(
-            text: model.channel,
-            action: changeAction,
-          )),
-          Value.withIcon(IconValue(
-            codePoint: Icons.arrow_right.codePoint,
-            action: changeAction,
-          )),
-        ]),
-      ]);
-    } else if (action == changeAction) {
-      var channels = model.channels;
-      final values = List<TextValue>.generate(
-          channels.length,
-          (index) => TextValue(
-                text: channels[index],
-                action: QuickAction.submit.$value | index,
-              ));
-      return Spec(title: _title, groups: [
-        Group(title: 'Select Channel', values: [
-          Value.withGrid(GridValue(
-            columns: 1,
-            values: values,
-          )),
-          Value.withButton(ButtonValue(
-            label: 'close',
-            action: QuickAction.cancel.$value,
-          )),
-        ]),
-      ]);
-    } else {
-      return Spec(title: _title, groups: [
-        Group(title: _title, values: [
-          Value.withText(TextValue(text: 'loading...')),
-        ]),
-      ]);
-    }
-  }
-}
-
-class _ChannelModel {
-  final ChannelControlProxy control;
-  final VoidCallback onChange;
-
-  late String _channel;
-  late List<String> _channels;
-
-  _ChannelModel({required this.control, required this.onChange}) {
-    loadCurrentChannel();
-    loadTargetChannels();
-  }
-
-  void dispose() {
-    control.ctrl.close();
-  }
-
-  String get channel => _channel;
-  set channel(String name) {
-    _channel = name;
-    control.setTarget(name);
-    onChange();
-  }
-
-  List<String> get channels => _channels;
-
-  void loadCurrentChannel() {
-    control.getTarget().then((name) {
-      _channel = name;
-      onChange();
-    });
-  }
-
-  void loadTargetChannels() {
-    control.getTargetList().then((channels) {
-      _channels = channels;
-    });
-  }
-}
diff --git a/session_shells/ermine/settings/lib/src/datetime.dart b/session_shells/ermine/settings/lib/src/datetime.dart
deleted file mode 100644
index 94be277..0000000
--- a/session_shells/ermine/settings/lib/src/datetime.dart
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'dart:async';
-
-import 'package:fidl_fuchsia_ui_remotewidgets/fidl_async.dart';
-import 'package:flutter/material.dart';
-import 'package:internationalization/strings.dart';
-import 'package:intl/intl.dart';
-import 'package:quickui/quickui.dart';
-
-// ignore_for_file: prefer_constructors_over_static_methods
-
-/// Defines a [UiSpec] for displaying date and time.
-class Datetime extends UiSpec {
-  // Localized strings.
-  static String get _title => Strings.dateTime;
-
-  // Icon for datetime title.
-  static IconValue get _icon =>
-      IconValue(codePoint: Icons.access_time.codePoint);
-  static const Duration refreshDuration = Duration(seconds: 1);
-
-  // Action to change timezone.
-  static int changeAction = QuickAction.details.$value;
-
-  late Timer _timer;
-
-  Datetime() {
-    _timer = Timer.periodic(refreshDuration, (_) => _onChange());
-    _onChange();
-  }
-
-  void _onChange() async {
-    spec = _specForDateTime();
-  }
-
-  @override
-  void update(Value value) async {}
-
-  @override
-  void dispose() {
-    _timer.cancel();
-  }
-
-  static Spec _specForDateTime() {
-    String dateTime = DateFormat.E().add_yMd().add_jm().format(DateTime.now());
-    return Spec(title: _title, groups: [
-      Group(
-          title: _title,
-          icon: _icon,
-          values: [Value.withText(TextValue(text: dateTime))]),
-    ]);
-  }
-}
diff --git a/session_shells/ermine/settings/lib/src/memory.dart b/session_shells/ermine/settings/lib/src/memory.dart
deleted file mode 100644
index a08ef05..0000000
--- a/session_shells/ermine/settings/lib/src/memory.dart
+++ /dev/null
@@ -1,108 +0,0 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'dart:async';
-import 'dart:math';
-
-import 'package:fidl_fuchsia_memory/fidl_async.dart';
-import 'package:fidl_fuchsia_ui_remotewidgets/fidl_async.dart';
-import 'package:flutter/material.dart';
-import 'package:fuchsia_services/services.dart' show Incoming;
-import 'package:internationalization/strings.dart';
-import 'package:quickui/quickui.dart';
-
-// ignore_for_file: prefer_constructors_over_static_methods
-
-/// Defines a [UiSpec] for visualizing memory.
-class Memory extends UiSpec {
-  static String get _memory => Strings.memory;
-
-  // Icon for memory title.
-  static IconValue get _icon =>
-      IconValue(codePoint: Icons.memory_outlined.codePoint);
-
-  late MemoryModel model;
-
-  Memory({required Monitor monitor, WatcherBinding? binding}) {
-    model = MemoryModel(
-      monitor: monitor,
-      binding: binding,
-      onChange: _onChange,
-    );
-  }
-
-  factory Memory.withSvcPath() {
-    final monitor = MonitorProxy();
-    Incoming.fromSvcPath().connectToService(monitor);
-    return Memory(monitor: monitor);
-  }
-
-  void _onChange() {
-    spec = _specForMemory(model.memory, model.memUsed, model.memTotal);
-  }
-
-  @override
-  void update(Value value) async {}
-
-  @override
-  void dispose() {
-    model.dispose();
-  }
-
-  static Spec _specForMemory(double value, double used, double total) {
-    String usedString = (used).toStringAsPrecision(3);
-    String totalString = (total).toStringAsPrecision(3);
-    return Spec(title: _memory, groups: [
-      Group(title: _memory, icon: _icon, values: [
-        Value.withText(TextValue(text: '${usedString}GB / ${totalString}GB')),
-      ]),
-    ]);
-  }
-}
-
-class MemoryModel {
-  final VoidCallback onChange;
-  final WatcherBinding _binding;
-  late double memUsed;
-  late double memTotal;
-  late double _memory;
-
-  MemoryModel({
-    required this.onChange,
-    required Monitor monitor,
-    WatcherBinding? binding,
-  }) : _binding = binding ?? WatcherBinding() {
-    monitor.watch(_binding.wrap(_MonitorWatcherImpl(this)));
-  }
-
-  void dispose() {
-    _binding.close();
-  }
-
-  double get memory => _memory;
-  set memory(double value) {
-    _memory = value;
-    onChange();
-  }
-
-  void updateMem(Stats stats) {
-    memUsed = _bytesToGB(stats.totalBytes - stats.freeBytes);
-    memTotal = _bytesToGB(stats.totalBytes);
-    memory = memUsed / memTotal;
-  }
-
-  double _bytesToGB(int bytes) {
-    return (bytes / pow(1024, 3));
-  }
-}
-
-class _MonitorWatcherImpl extends Watcher {
-  final MemoryModel memoryModel;
-  _MonitorWatcherImpl(this.memoryModel);
-
-  @override
-  Future<void> onChange(Stats stats) async {
-    memoryModel.updateMem(stats);
-  }
-}
diff --git a/session_shells/ermine/settings/lib/src/system_information.dart b/session_shells/ermine/settings/lib/src/system_information.dart
deleted file mode 100644
index e0fc6e5..0000000
--- a/session_shells/ermine/settings/lib/src/system_information.dart
+++ /dev/null
@@ -1,159 +0,0 @@
-// Copyright 2021 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'dart:async';
-
-import 'package:fidl_fuchsia_memory/fidl_async.dart';
-import 'package:fidl_fuchsia_session/fidl_async.dart' as session;
-import 'package:fidl_fuchsia_ui_remotewidgets/fidl_async.dart';
-import 'package:flutter/material.dart';
-import 'package:fuchsia_logger/logger.dart';
-import 'package:fuchsia_services/services.dart' show Incoming;
-import 'package:internationalization/strings.dart';
-import 'package:quickui/quickui.dart';
-
-import 'memory.dart' as memory;
-
-const licenseUrl =
-    'fuchsia-pkg://fuchsia.com/license_settings#meta/license_settings.cmx';
-const feedbackUrl =
-    'fuchsia-pkg://fuchsia.com/feedback_settings#meta/feedback_settings.cmx';
-
-// ignore_for_file: prefer_constructors_over_static_methods
-
-/// Defines a [UiSpec] for displaying system information.
-class SystemInformation extends UiSpec {
-  // Localized strings.
-  static String get _title => Strings.systemInformation;
-  static String get _memory => Strings.memory;
-  static String get _view => Strings.view;
-  static String get _loading => Strings.loading;
-  static String get _feedback => Strings.feedback;
-  static String get _openSource => Strings.openSource;
-  static String get _license => Strings.license;
-
-  // Icon for system information title.
-  static IconValue get _icon =>
-      IconValue(codePoint: Icons.info_outlined.codePoint);
-
-  // Action to launch license.
-  static int changeAction = QuickAction.details.$value;
-
-  // Memory model and variables
-  late memory.MemoryModel memoryModel;
-  late String usedMemory;
-  late String totalMemory;
-
-  SystemInformation({required Monitor monitor, WatcherBinding? binding}) {
-    memoryModel = memory.MemoryModel(
-        monitor: monitor, binding: binding, onChange: _onChangeMemory);
-    _onChange();
-  }
-
-  factory SystemInformation.withSvcPath() {
-    final monitor = MonitorProxy();
-    Incoming.fromSvcPath().connectToService(monitor);
-    return SystemInformation(monitor: monitor);
-  }
-
-  void _onChange() async {
-    spec = await _specForSystemInformation();
-  }
-
-  void _onChangeMemory() async {
-    usedMemory = (memoryModel.memUsed).toStringAsPrecision(3);
-    totalMemory = (memoryModel.memTotal).toStringAsPrecision(3);
-  }
-
-  @override
-  void update(Value value) async {
-    if (value.$tag == ValueTag.button &&
-        value.button!.action == QuickAction.cancel.$value) {
-      spec = await _specForSystemInformation();
-    } else if (value.$tag == ValueTag.text && value.text!.action > 0) {
-      if (value.text!.action == changeAction) {
-        spec = await _specForSystemInformation(changeAction);
-      } else {
-        final index = value.text!.action ^ QuickAction.submit.$value;
-        if (index == 1) {
-          await launchUrl(feedbackUrl);
-        }
-        if (index == 2) {
-          await launchUrl(licenseUrl);
-        }
-        spec = await _specForSystemInformation();
-      }
-    }
-  }
-
-  @override
-  void dispose() {
-    memoryModel.dispose();
-  }
-
-  Future<void> launchUrl(String url) async {
-    final proxy = session.ElementManagerProxy();
-    final elementController = session.ElementControllerProxy();
-
-    final incoming = Incoming.fromSvcPath()..connectToService(proxy);
-
-    final spec = session.ElementSpec(componentUrl: url);
-
-    await proxy
-        .proposeElement(spec, elementController.ctrl.request())
-        .catchError((err) {
-      log.shout('$err: Failed to propose element <$url>');
-    });
-
-    proxy.ctrl.close();
-    await incoming.close();
-  }
-
-  Future<Spec> _specForSystemInformation([int action = 0]) async {
-    if (action == 0 || action & QuickAction.cancel.$value > 0) {
-      return Spec(title: _title, groups: [
-        Group(title: _title, icon: _icon, values: [
-          Value.withText(TextValue(
-            text: _view,
-            action: changeAction,
-          )),
-          Value.withIcon(IconValue(
-            codePoint: Icons.arrow_right.codePoint,
-            action: changeAction,
-          )),
-        ]),
-      ]);
-    } else if (action == changeAction) {
-      return Spec(title: _title, groups: [
-        Group(title: '', values: [
-          Value.withGrid(GridValue(
-            columns: 2,
-            values: [
-              TextValue(text: '${_memory.toUpperCase()}'),
-              TextValue(text: '${usedMemory}GB / ${totalMemory}GB'),
-              TextValue(text: '${_feedback.toUpperCase()}'),
-              TextValue(
-                text: '${_view.toUpperCase()}',
-                action: QuickAction.submit.$value | 1,
-              ),
-              TextValue(
-                text: '${_openSource.toUpperCase()} ${_license.toUpperCase()}',
-              ),
-              TextValue(
-                text: '${_view.toUpperCase()}',
-                action: QuickAction.submit.$value | 2,
-              )
-            ],
-          )),
-        ]),
-      ]);
-    } else {
-      return Spec(title: _title, groups: [
-        Group(title: _title, values: [
-          Value.withText(TextValue(text: '$_loading...')),
-        ]),
-      ]);
-    }
-  }
-}
diff --git a/session_shells/ermine/settings/lib/src/timezone.dart b/session_shells/ermine/settings/lib/src/timezone.dart
deleted file mode 100644
index 32bfabf..0000000
--- a/session_shells/ermine/settings/lib/src/timezone.dart
+++ /dev/null
@@ -1,183 +0,0 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'dart:io';
-
-import 'package:async/async.dart';
-import 'package:fidl_fuchsia_intl/fidl_async.dart';
-import 'package:fidl_fuchsia_settings/fidl_async.dart';
-import 'package:fidl_fuchsia_ui_remotewidgets/fidl_async.dart';
-import 'package:flutter/material.dart';
-import 'package:fuchsia_services/services.dart' show Incoming;
-import 'package:internationalization/strings.dart';
-import 'package:quickui/quickui.dart';
-
-/// Defines a [UiSpec] for displaying and changing timezone.
-class TimeZone extends UiSpec {
-  // Localized strings.
-  static String get _title => Strings.timezone;
-
-  // Icon for timezone title.
-  static IconValue get _icon =>
-      IconValue(codePoint: Icons.access_time.codePoint);
-
-  // Action to change timezone.
-  static int changeAction = QuickAction.details.$value;
-
-  late _TimeZoneModel model;
-  Future<List<TimeZoneInfo>> Function() timeZonesProvider;
-
-  TimeZone({
-    required IntlProxy intlSettingsService,
-    required this.timeZonesProvider,
-  }) {
-    model = _TimeZoneModel(
-      intlSettingsService: intlSettingsService,
-      onChange: _onChange,
-    );
-  }
-
-  factory TimeZone.withSvcPath() {
-    final intlSettingsService = IntlProxy();
-    Incoming.fromSvcPath().connectToService(intlSettingsService);
-
-    final timeZonesLoader = _TimeZonesLoader();
-
-    final timezone = TimeZone(
-        intlSettingsService: intlSettingsService,
-        timeZonesProvider: timeZonesLoader.getList);
-    return timezone;
-  }
-
-  void _onChange() async {
-    spec = await _specForTimeZone(model);
-  }
-
-  @override
-  void update(Value value) async {
-    if (value.$tag == ValueTag.button &&
-        value.button!.action == QuickAction.cancel.$value) {
-      spec = await _specForTimeZone(model);
-    } else if (value.$tag == ValueTag.text && value.text!.action > 0) {
-      if (value.text!.action == changeAction) {
-        spec = await _specForTimeZone(model, changeAction);
-      } else {
-        final index = value.text!.action ^ QuickAction.submit.$value;
-        model.timeZoneId = (await timeZonesProvider())[index].zoneId;
-        spec = await _specForTimeZone(model);
-      }
-    }
-  }
-
-  @override
-  void dispose() {
-    model.dispose();
-  }
-
-  Future<Spec> _specForTimeZone(_TimeZoneModel model, [int action = 0]) async {
-    if (action == 0 || action & QuickAction.cancel.$value > 0) {
-      return Spec(title: _title, groups: [
-        Group(title: _title, icon: _icon, values: [
-          Value.withText(TextValue(
-            text: model.timeZoneId!,
-            action: changeAction,
-          )),
-          Value.withIcon(IconValue(
-            codePoint: Icons.arrow_right.codePoint,
-            action: changeAction,
-          )),
-        ]),
-      ]);
-    } else if (action == changeAction) {
-      var timeZones = await timeZonesProvider();
-      final values = List<TextValue>.generate(
-          timeZones.length,
-          (index) => TextValue(
-                text: timeZones[index].zoneId,
-                action: QuickAction.submit.$value | index,
-              ));
-      return Spec(title: _title, groups: [
-        Group(title: 'Select Timezone', values: [
-          Value.withGrid(GridValue(
-            columns: 1,
-            values: values,
-          )),
-          Value.withButton(ButtonValue(
-            label: 'close',
-            action: QuickAction.cancel.$value,
-          )),
-        ]),
-      ]);
-    } else {
-      return Spec(title: _title, groups: [
-        Group(title: _title, values: [
-          Value.withText(TextValue(
-            text: model.timeZoneId!,
-            action: changeAction,
-          )),
-        ]),
-      ]);
-    }
-  }
-}
-
-class _TimeZoneModel {
-  IntlProxy intlSettingsService;
-  final VoidCallback onChange;
-
-  IntlSettings? _intlSettings;
-
-  _TimeZoneModel({required this.intlSettingsService, required this.onChange}) {
-    // Get current timezone and watch it for changes.
-    intlSettingsService.watch().then(_onIntlSettingsChange);
-  }
-
-  Future<void> _onIntlSettingsChange(IntlSettings intlSettings) async {
-    bool timeZoneChanged = (_intlSettings == null) ||
-        (_intlSettings?.timeZoneId?.id != intlSettings.timeZoneId?.id);
-    _intlSettings = intlSettings;
-    if (timeZoneChanged) {
-      onChange();
-    }
-    // Use the FIDL "hanging get" pattern to request the next update.
-    await intlSettingsService.watch().then(_onIntlSettingsChange);
-  }
-
-  void dispose() {
-    intlSettingsService.ctrl.close();
-  }
-
-  String? get timeZoneId =>
-      _intlSettings == null ? null : _intlSettings?.timeZoneId?.id;
-  set timeZoneId(String? value) {
-    final IntlSettings newIntlSettings = IntlSettings(
-        locales: _intlSettings?.locales,
-        temperatureUnit: _intlSettings?.temperatureUnit,
-        timeZoneId: TimeZoneId(id: value!));
-    intlSettingsService.set(newIntlSettings);
-  }
-}
-
-// Information needed to render a time zone list entry.
-class TimeZoneInfo {
-  /// The ICU standard zone ID.
-  final String zoneId;
-
-  const TimeZoneInfo({required this.zoneId});
-}
-
-// Loads and caches a list of time zones from the Ermine package.
-class _TimeZonesLoader {
-  final _memoizer = AsyncMemoizer<List<TimeZoneInfo>>();
-
-  Future<List<TimeZoneInfo>> getList() async => _memoizer.runOnce(_loadList);
-
-  Future<List<TimeZoneInfo>> _loadList() async {
-    var file = File('/pkg/data/tz_ids.txt');
-    List<TimeZoneInfo> timeZones = (await file.readAsLines())
-        .map((id) => TimeZoneInfo(zoneId: id))
-        .toList();
-    return timeZones;
-  }
-}
diff --git a/session_shells/ermine/settings/lib/src/volume.dart b/session_shells/ermine/settings/lib/src/volume.dart
deleted file mode 100644
index 07a4189..0000000
--- a/session_shells/ermine/settings/lib/src/volume.dart
+++ /dev/null
@@ -1,135 +0,0 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'dart:async';
-
-import 'package:fidl_fuchsia_media/fidl_async.dart' as vol;
-import 'package:fidl_fuchsia_ui_remotewidgets/fidl_async.dart';
-import 'package:flutter/material.dart';
-import 'package:fuchsia_services/services.dart' show Incoming;
-import 'package:internationalization/strings.dart';
-import 'package:quickui/quickui.dart';
-
-// ignore_for_file: prefer_constructors_over_static_methods
-
-/// Defines a [UiSpec] for controlling device volume.
-class Volume extends UiSpec {
-  static const minVolumeAction = 1;
-  static const maxVolumeAction = 2;
-  static const changeVolumeAction = 3;
-
-  // Localized strings.
-  static String get _title => Strings.volume;
-  static String get _min => Strings.min;
-  static String get _max => Strings.max;
-
-  // Icon for volume title.
-  static IconValue get _icon =>
-      IconValue(codePoint: Icons.volume_up_outlined.codePoint);
-
-  late _VolumeModel model;
-
-  Volume(vol.AudioCoreProxy control) {
-    model = _VolumeModel(control: control, onChange: _onChange);
-  }
-
-  factory Volume.withSvcPath() {
-    final control = vol.AudioCoreProxy();
-    Incoming.fromSvcPath().connectToService(control);
-    return Volume(control);
-  }
-
-  void _onChange() {
-    spec = _specForVolume(model.volume);
-  }
-
-  @override
-  void update(Value value) async {
-    if (value.$tag == ValueTag.button) {
-      if (value.button!.action == minVolumeAction) {
-        model.volume = 0;
-      } else if (value.button!.action == maxVolumeAction) {
-        model.volume = 1;
-      }
-    } else if (value.$tag == ValueTag.progress) {
-      model.volume = value.progress!.value;
-    }
-  }
-
-  @override
-  void dispose() {
-    model.dispose();
-  }
-
-  static Spec _specForVolume(double value) {
-    String roundedVolume = (value * 100).round().toString();
-    return Spec(title: _title, groups: [
-      Group(title: _title, icon: _icon, values: [
-        Value.withText(TextValue(text: roundedVolume)),
-        Value.withProgress(
-            ProgressValue(value: value, action: changeVolumeAction)),
-        Value.withButton(ButtonValue(label: _min, action: minVolumeAction)),
-        Value.withButton(ButtonValue(label: _max, action: maxVolumeAction)),
-      ]),
-    ]);
-  }
-}
-
-class _VolumeModel {
-  static const double _minLevelGainDb = -45.0;
-  static const double _maxLevelGainDb = 0.0;
-
-  final vol.AudioCoreProxy control;
-  final VoidCallback onChange;
-
-  late double _volume;
-  late StreamSubscription _volumeSubscription;
-
-  _VolumeModel({required this.control, required this.onChange}) {
-    _volumeSubscription = control.systemGainMuteChanged.listen((response) {
-      volume = gainToLevel(response.gainDb);
-    });
-  }
-
-  void dispose() {
-    _volumeSubscription.cancel();
-  }
-
-  double get volume => _volume;
-  set volume(double value) {
-    _volume = value;
-    control.setSystemGain(levelToGain(value));
-    if (_volume == 0) {
-      control.setSystemMute(true);
-    } else {
-      control.setSystemMute(false);
-    }
-    onChange();
-  }
-
-  /// Converts a gain in db to an audio 'level' in the range 0.0 to 1.0
-  /// inclusive.
-  double gainToLevel(double gainDb) {
-    if (gainDb <= _minLevelGainDb) {
-      return 0.0;
-    } else if (gainDb >= _maxLevelGainDb) {
-      return 1.0;
-    } else {
-      //double ratio = gainDb / -_minLevelGainDb;
-      return 1.0 - gainDb / _minLevelGainDb;
-    }
-  }
-
-  /// Converts an audio 'level' in the range 0.0 to 1.0 inclusive to a gain in
-  /// db.
-  double levelToGain(double level) {
-    if (level <= 0.0) {
-      return _minLevelGainDb;
-    } else if (level >= 1.0) {
-      return _maxLevelGainDb;
-    } else {
-      return (1.0 - level) * _minLevelGainDb;
-    }
-  }
-}
diff --git a/session_shells/ermine/settings/pubspec.yaml b/session_shells/ermine/settings/pubspec.yaml
deleted file mode 100644
index 7c3a8e8..0000000
--- a/session_shells/ermine/settings/pubspec.yaml
+++ /dev/null
@@ -1,5 +0,0 @@
-# Copyright 2020 The Fuchsia Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-name: settings
\ No newline at end of file
diff --git a/session_shells/ermine/settings/test/battery_test.dart b/session_shells/ermine/settings/test/battery_test.dart
deleted file mode 100644
index 31a57c1..0000000
--- a/session_shells/ermine/settings/test/battery_test.dart
+++ /dev/null
@@ -1,209 +0,0 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'package:fidl/fidl.dart';
-import 'package:fidl_fuchsia_power/fidl_async.dart';
-import 'package:fidl_fuchsia_ui_remotewidgets/fidl_async.dart';
-import 'package:flutter_test/flutter_test.dart';
-import 'package:mockito/mockito.dart';
-import 'package:settings/settings.dart';
-
-void main() {
-  test('Battery', () async {
-    final monitorProxy = MockMonitorProxy();
-    final binding = MockBinding();
-
-    Battery battery = Battery(monitor: monitorProxy, binding: binding);
-
-    final BatteryInfoWatcher watcher =
-        verify(binding.wrap(captureAny)).captured.single;
-    await watcher.onChangeBatteryInfo(
-        _buildStats(5, BatteryStatus.ok, ChargeStatus.charging));
-
-    final spec = await battery.getSpec();
-
-    TextValue? text = spec.groups?.first.values
-        ?.where((v) => v.$tag == ValueTag.text)
-        .first
-        .text;
-    expect(spec.groups?.first.title, isNotNull);
-    expect(spec.groups?.first.values?.isEmpty, false);
-    expect(text?.text, '5%');
-
-    // Confirm battery icon present in title
-    expect(spec.groups?.first.icon, isNotNull);
-  });
-
-  test('Change Battery Level', () async {
-    final monitorProxy = MockMonitorProxy();
-    final binding = MockBinding();
-
-    Battery battery = Battery(monitor: monitorProxy, binding: binding);
-
-    final BatteryInfoWatcher watcher =
-        verify(binding.wrap(captureAny)).captured.single;
-    await watcher.onChangeBatteryInfo(
-        _buildStats(5, BatteryStatus.ok, ChargeStatus.charging));
-
-    final spec = await battery.getSpec();
-
-    TextValue? text = spec.groups?.first.values
-        ?.where((v) => v.$tag == ValueTag.text)
-        .first
-        .text;
-    expect(spec.groups?.first.title, isNotNull);
-    expect(spec.groups?.first.values?.isEmpty, false);
-    expect(text?.text, '5%');
-
-    // Change battery level
-    await watcher.onChangeBatteryInfo(
-        _buildStats(6, BatteryStatus.ok, ChargeStatus.notCharging));
-
-    final updatedSpec = await battery.getSpec();
-
-    text = updatedSpec.groups?.first.values
-        ?.where((v) => v.$tag == ValueTag.text)
-        .first
-        .text;
-    expect(spec.groups?.first.title, isNotNull);
-    expect(spec.groups?.first.values?.isEmpty, false);
-    expect(text?.text, '6%');
-
-    // Confirm battery icon present in title
-    expect(spec.groups?.first.icon, isNotNull);
-  });
-
-  test('Change Charging Status', () async {
-    final monitorProxy = MockMonitorProxy();
-    final binding = MockBinding();
-
-    Battery battery = Battery(monitor: monitorProxy, binding: binding);
-
-    final BatteryInfoWatcher watcher =
-        verify(binding.wrap(captureAny)).captured.single;
-    await watcher.onChangeBatteryInfo(
-        _buildStats(50, BatteryStatus.ok, ChargeStatus.notCharging));
-
-    Spec spec = await battery.getSpec();
-
-    TextValue? text = spec.groups?.first.values
-        ?.where((v) => v.$tag == ValueTag.text)
-        .first
-        .text;
-    expect(spec.groups?.first.title, isNotNull);
-    expect(spec.groups?.first.values?.isEmpty, false);
-    expect(text?.text, '50%');
-
-    // Change charging status
-    await watcher.onChangeBatteryInfo(
-        _buildStats(51, BatteryStatus.ok, ChargeStatus.charging));
-
-    spec = await battery.getSpec();
-    text = spec.groups?.first.values
-        ?.where((v) => v.$tag == ValueTag.text)
-        .first
-        .text;
-    expect(spec.groups?.first.title, isNotNull);
-    expect(spec.groups?.first.values?.isEmpty, false);
-    expect(text?.text, '51%');
-
-    // Confirm battery icon present in title
-    expect(spec.groups?.first.icon, isNotNull);
-  });
-
-  test('Low Battery Warning', () async {
-    final monitorProxy = MockMonitorProxy();
-    final binding = MockBinding();
-
-    Battery battery = Battery(monitor: monitorProxy, binding: binding);
-
-    final BatteryInfoWatcher watcher =
-        verify(binding.wrap(captureAny)).captured.single;
-    await watcher.onChangeBatteryInfo(
-        _buildStats(50, BatteryStatus.ok, ChargeStatus.notCharging));
-
-    Spec spec = await battery.getSpec();
-
-    TextValue? text = spec.groups?.first.values
-        ?.where((v) => v.$tag == ValueTag.text)
-        .first
-        .text;
-    expect(spec.groups?.first.title, isNotNull);
-    expect(spec.groups?.first.values?.isEmpty, false);
-    expect(text?.text, '50%');
-
-    // Change charge to <10% (low status)
-    await watcher.onChangeBatteryInfo(
-        _buildStats(9, BatteryStatus.ok, ChargeStatus.notCharging));
-
-    spec = await battery.getSpec();
-    text = spec.groups?.first.values
-        ?.where((v) => v.$tag == ValueTag.text)
-        .first
-        .text;
-    expect(spec.groups?.first.title, isNotNull);
-    expect(spec.groups?.first.values?.isEmpty, false);
-    expect(text?.text, '9%');
-
-    // Confirm battery icon present in title
-    expect(spec.groups?.first.icon, isNotNull);
-  });
-
-  test('Battery Full', () async {
-    final monitorProxy = MockMonitorProxy();
-    final binding = MockBinding();
-
-    Battery battery = Battery(monitor: monitorProxy, binding: binding);
-
-    final BatteryInfoWatcher watcher =
-        verify(binding.wrap(captureAny)).captured.single;
-    await watcher.onChangeBatteryInfo(
-        _buildStats(50, BatteryStatus.ok, ChargeStatus.notCharging));
-
-    Spec spec = await battery.getSpec();
-
-    TextValue? text = spec.groups?.first.values
-        ?.where((v) => v.$tag == ValueTag.text)
-        .first
-        .text;
-    expect(spec.groups?.first.title, isNotNull);
-    expect(spec.groups?.first.values?.isEmpty, false);
-    expect(text?.text, '50%');
-
-    // Change charge to 100% (full)
-    await watcher.onChangeBatteryInfo(
-        _buildStats(100, BatteryStatus.ok, ChargeStatus.notCharging));
-
-    spec = await battery.getSpec();
-    text = spec.groups?.first.values
-        ?.where((v) => v.$tag == ValueTag.text)
-        .first
-        .text;
-    expect(spec.groups?.first.title, isNotNull);
-    expect(spec.groups?.first.values?.isEmpty, false);
-    expect(text?.text, '100%');
-
-    // Confirm battery icon present in title
-    expect(spec.groups?.first.icon, isNotNull);
-  });
-}
-
-BatteryInfo _buildStats(
-    double power, BatteryStatus status, ChargeStatus charge) {
-  // ignore: missing_required_param
-  return BatteryInfo(
-    levelPercent: power,
-    status: status,
-    chargeStatus: charge,
-  );
-}
-
-// Mock classes.
-class MockMonitorProxy extends Mock implements BatteryManagerProxy {}
-
-class MockBinding extends Mock implements BatteryInfoWatcherBinding {
-  @override
-  InterfaceHandle<BatteryInfoWatcher> wrap(BatteryInfoWatcher? impl) =>
-      super.noSuchMethod(Invocation.method(#wrap, [impl]));
-}
diff --git a/session_shells/ermine/settings/test/brightness_test.dart b/session_shells/ermine/settings/test/brightness_test.dart
deleted file mode 100644
index 6146bd1..0000000
--- a/session_shells/ermine/settings/test/brightness_test.dart
+++ /dev/null
@@ -1,110 +0,0 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'package:fidl/fidl.dart';
-import 'package:fidl_fuchsia_ui_brightness/fidl_async.dart';
-import 'package:fidl_fuchsia_ui_remotewidgets/fidl_async.dart';
-import 'package:flutter_test/flutter_test.dart';
-import 'package:mockito/mockito.dart';
-import 'package:settings/settings.dart';
-
-void main() {
-  late MockControl control;
-  late MockProxyController mockProxy;
-
-  setUp(() async {
-    control = MockControl();
-    mockProxy = MockProxyController();
-    when(control.ctrl).thenReturn(mockProxy);
-  });
-
-  tearDown(() async {
-    verify(mockProxy.close()).called(1);
-  });
-
-  test('Brightness', () async {
-    when(control.watchAutoBrightness()).thenAnswer((_) => Future.value(false));
-    when(control.watchCurrentBrightness()).thenAnswer((_) => Future.value(0.8));
-    final brightness = Brightness(control);
-
-    // Should receive brightness spec.
-    Spec spec = await brightness.getSpec();
-    expect(spec.groups?.first.title, isNotNull);
-    expect(spec.groups?.first.values?.isEmpty, false);
-
-    ProgressValue? progress = spec.groups?.first.values
-        ?.where((v) => v.$tag == ValueTag.progress)
-        .first
-        .progress;
-    expect(progress, isNotNull);
-    expect(progress?.value, 0.8);
-
-    brightness.dispose();
-  });
-
-  test('Change Brightness', () async {
-    when(control.watchAutoBrightness()).thenAnswer((_) => Future.value(true));
-    when(control.watchCurrentBrightness()).thenAnswer((_) => Future.value(0.5));
-
-    // Should receive brightness spec.
-    final brightness = Brightness(control);
-    Spec spec = await brightness.getSpec();
-
-    // Now change the brightness.
-    brightness.update(Value.withProgress(ProgressValue(
-      value: 0.3,
-      action: Brightness.progressAction,
-    )));
-
-    verify(control.setManualBrightness(0.3));
-
-    // Should follow immediately by brightness spec.
-    spec = await brightness.getSpec();
-    expect(spec.groups?.first.title, isNotNull);
-    expect(spec.groups?.first.values?.isEmpty, false);
-
-    ProgressValue? progress = spec.groups?.first.values
-        ?.where((v) => v.$tag == ValueTag.progress)
-        .first
-        .progress;
-    expect(progress, isNotNull);
-    expect(progress?.value, 0.3);
-
-    brightness.dispose();
-  });
-
-  test('Set auto Brightness', () async {
-    when(control.watchAutoBrightness()).thenAnswer((_) => Future.value(false));
-    when(control.watchCurrentBrightness()).thenAnswer((_) => Future.value(0.5));
-
-    // Should receive brightness spec.
-    final brightness = Brightness(control);
-    Spec spec = await brightness.getSpec();
-
-    // Now set brightness to auto.
-    brightness.update(Value.withButton(ButtonValue(
-      label: 'label',
-      action: Brightness.autoAction,
-    )));
-
-    verify(control.setAutoBrightness());
-
-    // Should follow immediately by brightness spec.
-    spec = await brightness.getSpec();
-    expect(spec.groups?.first.title, isNotNull);
-    expect(spec.groups?.first.values?.isEmpty, false);
-
-    // Should be MISSING a button to set auto brightness.
-    final hasButton =
-        spec.groups?.first.values?.any((v) => v.$tag == ValueTag.button);
-    expect(hasButton, isFalse);
-
-    brightness.dispose();
-  });
-}
-
-class MockControl extends Mock implements ControlProxy {}
-
-class MockProxyController extends Mock
-    implements AsyncProxyController<Control> {}
diff --git a/session_shells/ermine/settings/test/channel_test.dart b/session_shells/ermine/settings/test/channel_test.dart
deleted file mode 100644
index 392ddb1..0000000
--- a/session_shells/ermine/settings/test/channel_test.dart
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright 2021 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'package:fidl_fuchsia_ui_remotewidgets/fidl_async.dart';
-import 'package:fidl_fuchsia_update_channelcontrol/fidl_async.dart';
-import 'package:flutter_test/flutter_test.dart';
-import 'package:mockito/mockito.dart';
-import 'package:settings/settings.dart';
-
-const List<String> channels = [
-  'channelA',
-  'channelB',
-];
-
-void main() {
-  late MockControl control;
-
-  setUp(() async {
-    control = MockControl();
-  });
-
-  test('Default Channel Spec', () async {
-    when(control.getTarget()).thenAnswer((_) => Future.value(channels[0]));
-    when(control.getTargetList()).thenAnswer((_) => Future.value(channels));
-
-    final channel = Channel(control);
-    Spec spec = await channel.getSpec();
-
-    expect(spec.title, 'Channel');
-    expect(spec.groups?.first.values?.first.text?.text, channels[0]);
-  });
-
-  test('Change Channel', () async {
-    when(control.getTarget()).thenAnswer((_) => Future.value(channels[0]));
-    when(control.getTargetList()).thenAnswer((_) => Future.value(channels));
-
-    final channel = Channel(control);
-    Spec specA = await channel.getSpec();
-
-    expect(specA.title, 'Channel');
-    expect(specA.groups?.first.values?.first.text?.text, channels[0]);
-
-    // Change channel
-    channel.model.channel = channels[1];
-
-    // Wait one event cycle for the change
-    await channel.getSpec();
-    Spec specB = await channel.getSpec();
-    expect(specB.groups?.first.values?.first.text?.text, channels[1]);
-  });
-}
-
-class MockControl extends Mock implements ChannelControlProxy {}
diff --git a/session_shells/ermine/settings/test/datetime_test.dart b/session_shells/ermine/settings/test/datetime_test.dart
deleted file mode 100644
index 376ac37..0000000
--- a/session_shells/ermine/settings/test/datetime_test.dart
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'package:flutter_test/flutter_test.dart';
-import 'package:settings/settings.dart';
-
-void main() {
-  test('Datetime', () async {
-    final stopWatch = Stopwatch()..start();
-    final datetime = Datetime();
-    var spec = await datetime.getSpec();
-
-    expect(spec.title, isNotNull);
-    expect(spec.groups?.first.values?.first.text?.text, isNotNull);
-
-    // Make sure the next update is received after [Datetime.refreshDuration].
-    spec = await datetime.getSpec();
-    stopWatch.stop();
-
-    expect(stopWatch.elapsed >= Datetime.refreshDuration, true);
-  });
-}
diff --git a/session_shells/ermine/settings/test/memory_test.dart b/session_shells/ermine/settings/test/memory_test.dart
deleted file mode 100644
index 6dcb69b..0000000
--- a/session_shells/ermine/settings/test/memory_test.dart
+++ /dev/null
@@ -1,149 +0,0 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'dart:math';
-
-import 'package:fidl/fidl.dart';
-import 'package:fidl_fuchsia_memory/fidl_async.dart' as mem;
-import 'package:fidl_fuchsia_ui_remotewidgets/fidl_async.dart';
-import 'package:flutter_test/flutter_test.dart';
-import 'package:mockito/mockito.dart';
-import 'package:settings/settings.dart';
-
-void main() {
-  test('Memory', () async {
-    final monitorProxy = MockMonitorProxy();
-    final binding = MockBinding();
-    Memory memory = Memory(monitor: monitorProxy, binding: binding);
-
-    final mem.Watcher watcher =
-        verify(binding.wrap(captureAny)).captured.single;
-    await watcher.onChange(_buildStats(0.5));
-
-    // Should receive memory spec
-    Spec spec = await memory.getSpec();
-    expect(spec.groups?.first.title, isNotNull);
-    expect(spec.groups?.first.values?.isEmpty, false);
-
-    // Confirm text value is correct
-    TextValue? text = spec.groups?.first.values
-        ?.where((v) => v.$tag == ValueTag.text)
-        .first
-        .text;
-    expect(text?.text, '0.500GB / 1.00GB');
-
-    memory.dispose();
-  });
-
-  test('Change Memory', () async {
-    final monitorProxy = MockMonitorProxy();
-    final binding = MockBinding();
-    Memory memory = Memory(monitor: monitorProxy, binding: binding);
-
-    final mem.Watcher watcher =
-        verify(binding.wrap(captureAny)).captured.single;
-    await watcher.onChange(_buildStats(0.5));
-
-    // Should receive memory spec
-    Spec spec = await memory.getSpec();
-    expect(spec.groups?.first.title, isNotNull);
-    expect(spec.groups?.first.values?.isEmpty, false);
-
-    // Confirm text value is correct
-    TextValue? text = spec.groups?.first.values
-        ?.where((v) => v.$tag == ValueTag.text)
-        .first
-        .text;
-    expect(text?.text, '0.500GB / 1.00GB');
-
-    // Update memory usage.
-    await watcher.onChange(_buildStats(0.7));
-
-    spec = await memory.getSpec();
-    expect(spec.groups?.first.title, isNotNull);
-    expect(spec.groups?.first.values?.isEmpty, false);
-
-    // Confirm text value is correct
-    text = spec.groups?.first.values
-        ?.where((v) => v.$tag == ValueTag.text)
-        .first
-        .text;
-    expect(text?.text, '0.300GB / 1.00GB');
-
-    memory.dispose();
-  });
-
-  test('Min Memory', () async {
-    final monitorProxy = MockMonitorProxy();
-    final binding = MockBinding();
-    Memory memory = Memory(monitor: monitorProxy, binding: binding);
-
-    final mem.Watcher watcher =
-        verify(binding.wrap(captureAny)).captured.single;
-    await watcher.onChange(_buildStats(1));
-
-    // Should receive memory spec
-    Spec spec = await memory.getSpec();
-    expect(spec.groups?.first.title, isNotNull);
-    expect(spec.groups?.first.values?.isEmpty, false);
-
-    // Confirm text value is correct
-    TextValue? text = spec.groups?.first.values
-        ?.where((v) => v.$tag == ValueTag.text)
-        .first
-        .text;
-    expect(text?.text, '0.00GB / 1.00GB');
-
-    memory.dispose();
-  });
-
-  test('Max Memory', () async {
-    final monitorProxy = MockMonitorProxy();
-    final binding = MockBinding();
-    Memory memory = Memory(monitor: monitorProxy, binding: binding);
-
-    final mem.Watcher watcher =
-        verify(binding.wrap(captureAny)).captured.single;
-    await watcher.onChange(_buildStats(0));
-
-    // Should receive memory spec
-    Spec spec = await memory.getSpec();
-    expect(spec.groups?.first.title, isNotNull);
-    expect(spec.groups?.first.values?.isEmpty, false);
-
-    // Confirm text value is correct
-    TextValue? text = spec.groups?.first.values
-        ?.where((v) => v.$tag == ValueTag.text)
-        .first
-        .text;
-    expect(text?.text, '1.00GB / 1.00GB');
-
-    memory.dispose();
-  });
-}
-
-int get gB => pow(1024, 3).toInt();
-
-mem.Stats _buildStats(double bytes) {
-  return mem.Stats(
-    totalBytes: 1 * gB,
-    freeBytes: (bytes * gB).toInt(),
-    wiredBytes: 0,
-    totalHeapBytes: 0,
-    freeHeapBytes: 0,
-    vmoBytes: 0,
-    mmuOverheadBytes: 0,
-    ipcBytes: 0,
-    otherBytes: 0,
-  );
-}
-
-// Mock classes.
-class MockMonitorProxy extends Mock implements mem.MonitorProxy {}
-
-class MockBinding extends Mock implements mem.WatcherBinding {
-  @override
-  InterfaceHandle<mem.Watcher> wrap(mem.Watcher? impl) =>
-      super.noSuchMethod(Invocation.method(#wrap, [impl]));
-}
diff --git a/session_shells/ermine/settings/test/system_information_test.dart b/session_shells/ermine/settings/test/system_information_test.dart
deleted file mode 100644
index 73a3d05..0000000
--- a/session_shells/ermine/settings/test/system_information_test.dart
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright 2021 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'package:fidl/fidl.dart';
-import 'package:fidl_fuchsia_memory/fidl_async.dart' as mem;
-import 'package:flutter_test/flutter_test.dart';
-import 'package:mockito/mockito.dart';
-import 'package:settings/settings.dart';
-
-void main() {
-  test('System Information', () async {
-    final monitorProxy = MockMonitorProxy();
-    final binding = MockBinding();
-    final sysInfo = SystemInformation(monitor: monitorProxy, binding: binding);
-    var spec = await sysInfo.getSpec();
-
-    // Should receive system information spec.
-    expect(spec.title, isNotNull);
-    expect(spec.groups?.first.values?.first.text?.text, isNotNull);
-    expect(spec.groups?.first.values?.first.text?.text, 'View');
-
-    sysInfo.dispose();
-  });
-}
-
-// Mock classes.
-class MockMonitorProxy extends Mock implements mem.MonitorProxy {}
-
-class MockBinding extends Mock implements mem.WatcherBinding {
-  @override
-  InterfaceHandle<mem.Watcher> wrap(mem.Watcher? impl) =>
-      super.noSuchMethod(Invocation.method(#wrap, [impl]));
-}
diff --git a/session_shells/ermine/settings/test/timezone_test.dart b/session_shells/ermine/settings/test/timezone_test.dart
deleted file mode 100644
index 48d7aed..0000000
--- a/session_shells/ermine/settings/test/timezone_test.dart
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'package:fidl/fidl.dart';
-import 'package:fidl_fuchsia_intl/fidl_async.dart' as fintl;
-import 'package:fidl_fuchsia_settings/fidl_async.dart';
-import 'package:flutter_test/flutter_test.dart';
-import 'package:mockito/mockito.dart';
-import 'package:settings/settings.dart';
-
-const List<TimeZoneInfo> timeZones = [
-  TimeZoneInfo(zoneId: 'Test/A'),
-  TimeZoneInfo(zoneId: 'Test/B'),
-];
-
-void main() {
-  test('Change Timezone', () async {
-    var response = 'tz1';
-
-    final intlSettingsProxy = MockIntlProxy();
-    final intlSettingsProxyController = MockIntlProxyController();
-
-    when(intlSettingsProxy.ctrl).thenReturn(intlSettingsProxyController);
-    when(intlSettingsProxy.watch())
-        .thenAnswer((_) => Future<IntlSettings>.value(IntlSettings(
-              timeZoneId: fintl.TimeZoneId(id: response),
-            )));
-
-    TimeZone timeZone = TimeZone(
-        intlSettingsService: intlSettingsProxy,
-        timeZonesProvider: () => Future.value(timeZones));
-    final specA = await timeZone.getSpec();
-    expect(specA.groups?.first.values?.first.text?.text, response);
-
-    response = 'tz2';
-    // Wait one event cycle for the change.
-    await timeZone.getSpec();
-    final specB = await timeZone.getSpec();
-    expect(specB.groups?.first.values?.first.text?.text, response);
-
-    timeZone.dispose();
-  });
-}
-
-// Mock classes.
-class MockIntlProxy extends Mock implements IntlProxy {}
-
-class MockIntlProxyController extends Mock
-    implements AsyncProxyController<Intl> {}
diff --git a/session_shells/ermine/settings/test/volume_test.dart b/session_shells/ermine/settings/test/volume_test.dart
deleted file mode 100644
index 4d78a44..0000000
--- a/session_shells/ermine/settings/test/volume_test.dart
+++ /dev/null
@@ -1,231 +0,0 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'dart:async';
-
-import 'package:fidl_fuchsia_media/fidl_async.dart' as vol;
-import 'package:fidl_fuchsia_ui_remotewidgets/fidl_async.dart';
-import 'package:flutter_test/flutter_test.dart';
-import 'package:mockito/mockito.dart';
-import 'package:settings/settings.dart';
-
-void main() {
-  test('Volume', () async {
-    final control = MockControl();
-    when(control.systemGainMuteChanged)
-        .thenAnswer((response) => _buildStats(-9, false));
-
-    Volume volume = Volume(control);
-
-    // Should receive volume spec.
-    Spec spec = await volume.getSpec();
-    expect(spec.groups?.first.title, isNotNull);
-    expect(spec.groups?.first.values?.isEmpty, false);
-
-    // Confirm progress value is correct
-    ProgressValue? progress = spec.groups?.first.values
-        ?.where((v) => v.$tag == ValueTag.progress)
-        .first
-        .progress;
-    expect(progress, isNotNull);
-    expect(progress?.value, 0.8);
-
-    // Confirm text displayed is correct
-    TextValue? text = spec.groups?.first.values
-        ?.where((v) => v.$tag == ValueTag.text)
-        .first
-        .text;
-    expect(text?.text, '80');
-
-    // Confirm min & max buttons are present
-    Iterable? hasButtons =
-        spec.groups?.first.values?.where((v) => v.$tag == ValueTag.button);
-    expect(hasButtons?.length, 2);
-
-    volume.dispose();
-  });
-
-  test('Change Volume', () async {
-    final control = MockControl();
-    when(control.systemGainMuteChanged)
-        .thenAnswer((_) => _buildStats(-9, false));
-
-    Volume volume = Volume(control);
-
-    // Should receive volume spec.
-    Spec spec = await volume.getSpec();
-    expect(spec.groups?.first.title, isNotNull);
-    expect(spec.groups?.first.values?.isEmpty, false);
-
-    // Confirm progress value is correct
-    ProgressValue? progress = spec.groups?.first.values
-        ?.where((v) => v.$tag == ValueTag.progress)
-        .first
-        .progress;
-    expect(progress, isNotNull);
-    expect(progress?.value, 0.8);
-
-    // Confirm text displayed is correct
-    TextValue? text = spec.groups?.first.values
-        ?.where((v) => v.$tag == ValueTag.text)
-        .first
-        .text;
-    expect(text?.text, '80');
-
-    // Change volume level
-    volume.model.volume = .9;
-
-    // Should receive volume spec.
-    spec = await volume.getSpec();
-    expect(spec.groups?.first.title, isNotNull);
-    expect(spec.groups?.first.values?.isEmpty, false);
-
-    progress = spec.groups?.first.values
-        ?.where((v) => v.$tag == ValueTag.progress)
-        .first
-        .progress;
-    expect(progress, isNotNull);
-    expect(progress?.value, 0.9);
-
-    // Confirm text displayed is correct
-    text = spec.groups?.first.values
-        ?.where((v) => v.$tag == ValueTag.text)
-        .first
-        .text;
-    expect(text?.text, '90');
-
-    // Confirm min & max buttons are present
-    Iterable? hasButtons =
-        spec.groups?.first.values?.where((v) => v.$tag == ValueTag.button);
-    expect(hasButtons?.length, 2);
-
-    volume.dispose();
-  });
-
-  test('Max Volume', () async {
-    final control = MockControl();
-    when(control.systemGainMuteChanged)
-        .thenAnswer((_) => _buildStats(0, false));
-
-    Volume volume = Volume(control);
-
-    // Should receive volume spec.
-    Spec spec = await volume.getSpec();
-    expect(spec.groups?.first.title, isNotNull);
-    expect(spec.groups?.first.values?.isEmpty, false);
-
-    // Confirm progress value is correct
-    ProgressValue? progress = spec.groups?.first.values
-        ?.where((v) => v.$tag == ValueTag.progress)
-        .first
-        .progress;
-    expect(progress, isNotNull);
-    expect(progress?.value, 1);
-
-    // Confirm text displayed is correct
-    TextValue? text = spec.groups?.first.values
-        ?.where((v) => v.$tag == ValueTag.text)
-        .first
-        .text;
-    expect(text?.text, '100');
-
-    // Change volume level above max accepted dB
-    volume.model.volume = volume.model.gainToLevel(10);
-
-    // Should receive volume spec.
-    spec = await volume.getSpec();
-    expect(spec.groups?.first.title, isNotNull);
-    expect(spec.groups?.first.values?.isEmpty, false);
-
-    // Confirm volume is still at max
-    progress = spec.groups?.first.values
-        ?.where((v) => v.$tag == ValueTag.progress)
-        .first
-        .progress;
-    expect(progress, isNotNull);
-    expect(progress?.value, 1);
-
-    // Confirm text displayed is correct
-    text = spec.groups?.first.values
-        ?.where((v) => v.$tag == ValueTag.text)
-        .first
-        .text;
-    expect(text?.text, '100');
-
-    // Confirm min & max buttons are present
-    Iterable? hasButtons =
-        spec.groups?.first.values?.where((v) => v.$tag == ValueTag.button);
-    expect(hasButtons?.length, 2);
-
-    volume.dispose();
-  });
-
-  test('Min Volume', () async {
-    final control = MockControl();
-    when(control.systemGainMuteChanged)
-        .thenAnswer((_) => _buildStats(-45, false));
-
-    Volume volume = Volume(control);
-
-    // Should receive volume spec.
-    Spec spec = await volume.getSpec();
-    expect(spec.groups?.first.title, isNotNull);
-    expect(spec.groups?.first.values?.isEmpty, false);
-
-    // Confirm progress value is correct
-    ProgressValue? progress = spec.groups?.first.values
-        ?.where((v) => v.$tag == ValueTag.progress)
-        .first
-        .progress;
-    expect(progress, isNotNull);
-    expect(progress?.value, 0);
-
-    // Confirm text displayed is correct
-    TextValue? text = spec.groups?.first.values
-        ?.where((v) => v.$tag == ValueTag.text)
-        .first
-        .text;
-    expect(text?.text, '0');
-
-    // Change volume level below min accepted dB
-    volume.model.volume = volume.model.gainToLevel(-55);
-
-    // Should receive volume spec.
-    spec = await volume.getSpec();
-    expect(spec.groups?.first.title, isNotNull);
-    expect(spec.groups?.first.values?.isEmpty, false);
-
-    // Confirm volume is still at min
-    progress = spec.groups?.first.values
-        ?.where((v) => v.$tag == ValueTag.progress)
-        .first
-        .progress;
-    expect(progress, isNotNull);
-    expect(progress?.value, 0);
-
-    // Confirm text displayed is correct
-    text = spec.groups?.first.values
-        ?.where((v) => v.$tag == ValueTag.text)
-        .first
-        .text;
-    expect(text?.text, '0');
-
-    // Confirm min & max buttons are present
-    Iterable? hasButtons =
-        spec.groups?.first.values?.where((v) => v.$tag == ValueTag.button);
-    expect(hasButtons?.length, 2);
-
-    volume.dispose();
-  });
-}
-
-Stream<vol.AudioCore$SystemGainMuteChanged$Response> _buildStats(
-    double gainDB, bool muted) {
-  return Stream.value(
-      vol.AudioCore$SystemGainMuteChanged$Response(gainDB, muted));
-}
-
-class MockControl extends Mock implements vol.AudioCoreProxy {}
-
-class MockReponse extends Mock implements vol.AudioCore {}
diff --git a/session_shells/ermine/shell/BUILD.gn b/session_shells/ermine/shell/BUILD.gn
index f2003d4..cd4bac3 100644
--- a/session_shells/ermine/shell/BUILD.gn
+++ b/session_shells/ermine/shell/BUILD.gn
@@ -16,7 +16,7 @@
   ermine_app_entries = "config/app_launch_entries.json"
 
   # Whether or not to launch screensaver.
-  ermine_start_screensaver = true
+  ermine_start_screensaver = false
 }
 
 dart_library("lib") {
@@ -66,8 +66,7 @@
     "src/widgets/app_view.dart",
     "src/widgets/dialogs/dialog.dart",
     "src/widgets/dialogs/dialogs.dart",
-    "src/widgets/dialogs/text_only_dialog.dart",
-    "src/widgets/dialogs/textfield_dialog.dart",
+    "src/widgets/dialogs/password_prompt.dart",
     "src/widgets/launch_button.dart",
     "src/widgets/overlays.dart",
     "src/widgets/quick_settings.dart",
@@ -87,6 +86,7 @@
 
   deps = [
     "//sdk/dart/fidl",
+    "//sdk/dart/fuchsia",
     "//sdk/dart/fuchsia_inspect",
     "//sdk/dart/fuchsia_internationalization_flutter",
     "//sdk/dart/fuchsia_logger",
@@ -95,17 +95,16 @@
     "//sdk/dart/fuchsia_services",
     "//sdk/dart/zircon",
     "//sdk/fidl/fuchsia.buildinfo",
-    "//sdk/fidl/fuchsia.device.manager",
     "//sdk/fidl/fuchsia.element",
+    "//sdk/fidl/fuchsia.hardware.power.statecontrol",
     "//sdk/fidl/fuchsia.intl",
     "//sdk/fidl/fuchsia.media",
     "//sdk/fidl/fuchsia.media.audio",
     "//sdk/fidl/fuchsia.memory",
     "//sdk/fidl/fuchsia.net.interfaces",
-    "//sdk/fidl/fuchsia.power",
+    "//sdk/fidl/fuchsia.power.battery",
     "//sdk/fidl/fuchsia.settings",
     "//sdk/fidl/fuchsia.ssh",
-    "//sdk/fidl/fuchsia.sys",
     "//sdk/fidl/fuchsia.ui.activity",
     "//sdk/fidl/fuchsia.ui.app",
     "//sdk/fidl/fuchsia.ui.brightness",
@@ -120,7 +119,6 @@
     "//sdk/fidl/fuchsia.wlan.policy",
     "//src/experiences/session_shells/ermine/internationalization",
     "//src/experiences/session_shells/ermine/keyboard_shortcuts",
-    "//src/experiences/session_shells/ermine/settings",
     "//src/experiences/session_shells/ermine/utils:ermine_utils",
     "//third_party/dart-pkg/git/flutter/packages/flutter",
     "//third_party/dart-pkg/git/flutter/packages/flutter_driver",
@@ -143,7 +141,7 @@
     main_dart = "main.dart"
   }
   component_name = "ermine"
-  manifest = "meta/ermine.cmx"
+  manifest = "meta/ermine.cml"
   deps = [
     ":app_launch_entries_resource",
     ":lib",
diff --git a/session_shells/ermine/shell/config/app_launch_entries.json b/session_shells/ermine/shell/config/app_launch_entries.json
index fee33be..66e69d6 100644
--- a/session_shells/ermine/shell/config/app_launch_entries.json
+++ b/session_shells/ermine/shell/config/app_launch_entries.json
@@ -1,8 +1,9 @@
 [
     {
-        "icon": "images/SimpleBrowser-icon-2x.png",
-        "title": "Simple Browser",
-        "url": "fuchsia-pkg://fuchsia.com/simple-browser#meta/simple-browser.cmx"
+        "element_manager_name": "fuchsia.element.Manager-chrome",
+        "icon": "images/Chromium-icon-2x.png",
+        "title": "Chromium",
+        "url": "fuchsia-pkg://fuchsia.com/chrome#meta/chrome.cm"
     },
     {
         "icon": "images/SpinningSquare-icon-2x.png",
@@ -14,4 +15,4 @@
         "title": "Terminal",
         "url": "fuchsia-pkg://fuchsia.com/terminal#meta/terminal.cmx"
     }
-]
+]
\ No newline at end of file
diff --git a/session_shells/ermine/shell/config/app_launch_entries_with_chromium.json b/session_shells/ermine/shell/config/app_launch_entries_with_chromium.json
deleted file mode 100644
index 55d8f17..0000000
--- a/session_shells/ermine/shell/config/app_launch_entries_with_chromium.json
+++ /dev/null
@@ -1,17 +0,0 @@
-[
-    {
-        "icon": "images/Chromium-icon-2x.png",
-        "title": "Chromium",
-        "url": "fuchsia-pkg://fuchsia.com/chrome#meta/chrome_v1.cmx"
-    },
-    {
-        "icon": "images/SpinningSquare-icon-2x.png",
-        "title": "Spinning Square",
-        "url": "fuchsia-pkg://fuchsia.com/spinning-square-rs#meta/spinning-square-rs.cmx"
-    },
-    {
-        "icon": "images/Terminal-icon-2x.png",
-        "title": "Terminal",
-        "url": "fuchsia-pkg://fuchsia.com/terminal#meta/terminal.cmx"
-    }
-]
diff --git a/session_shells/ermine/shell/config/keyboard_shortcuts.json b/session_shells/ermine/shell/config/keyboard_shortcuts.json
index f0b5648..8096e93 100644
--- a/session_shells/ermine/shell/config/keyboard_shortcuts.json
+++ b/session_shells/ermine/shell/config/keyboard_shortcuts.json
@@ -181,5 +181,15 @@
             "exclusive": false,
             "localizedDescription": "increaseVolumeKeyboardShortcut"
         }
+    ],
+    "logout": [
+        {
+            "char": "q",
+            "chord": "Shift + Ctrl + q",
+            "description": "Logout",
+            "exclusive": true,
+            "modifier": "shift + control",
+            "localizedDescription": "logoutKeyboardShortcut"
+        }
     ]
 }
\ No newline at end of file
diff --git a/session_shells/ermine/shell/lib/src/services/focus_service.dart b/session_shells/ermine/shell/lib/src/services/focus_service.dart
index af7b04e..6a9d728 100644
--- a/session_shells/ermine/shell/lib/src/services/focus_service.dart
+++ b/session_shells/ermine/shell/lib/src/services/focus_service.dart
@@ -22,21 +22,42 @@
   late final ValueChanged<ViewHandle> onFocusMoved;
 
   final _focusChainListenerBinding = FocusChainListenerBinding();
+  late final StreamSubscription<bool> _focusSubscription;
 
   // Holds the currently focused child view. Null, if shell has focus.
   ViewState? focusedChildView;
 
+  // Temporary variable to guard against FocusChain overwriting the values
+  // set by _onHostFocusChanged(). Note that Flatland doesn't work with
+  // FocusChain.
+  // TODO(fxbug.dev/93446): Remove this along with FocusChain subscription after
+  // enabling Flatland by default.
+  bool flatlandHasMovedFocusToChild = false;
+
   FocusService(ViewRef viewRef) : hostView = ViewHandle(viewRef) {
     final registryProxy = FocusChainListenerRegistryProxy();
     Incoming.fromSvcPath().connectToService(registryProxy);
     registryProxy.register(_focusChainListenerBinding.wrap(this));
     registryProxy.ctrl.close();
+    _focusSubscription =
+        FocusState.instance.stream().listen(_onHostFocusChanged);
   }
 
   void dispose() {
+    _focusSubscription.cancel();
     _focusChainListenerBinding.close(0);
   }
 
+  void _onHostFocusChanged(bool focused) {
+    if (focused) {
+      onFocusMoved(hostView);
+      flatlandHasMovedFocusToChild = false;
+    } else if (focusedChildView != null) {
+      onFocusMoved(focusedChildView!.view);
+      flatlandHasMovedFocusToChild = true;
+    }
+  }
+
   void setFocusOnHostView() {
     focusedChildView?.cancelSetFocus();
     focusedChildView = null;
@@ -84,6 +105,8 @@
     final index = chain.lastIndexOf(hostView);
     final childView = chain[index + 1];
 
-    onFocusMoved(childView);
+    if (!flatlandHasMovedFocusToChild) {
+      onFocusMoved(childView);
+    }
   }
 }
diff --git a/session_shells/ermine/shell/lib/src/services/launch_service.dart b/session_shells/ermine/shell/lib/src/services/launch_service.dart
index 5b184c5..8ae1cf4 100644
--- a/session_shells/ermine/shell/lib/src/services/launch_service.dart
+++ b/session_shells/ermine/shell/lib/src/services/launch_service.dart
@@ -18,11 +18,18 @@
 class LaunchService {
   late final ControllerClosedCallback onControllerClosed;
 
-  Future<ControllerProxy> launch(String title, String url) async {
+  Future<ControllerProxy> launch(String title, String url,
+      {String? alternateServiceName}) async {
     final elementController = ControllerProxy();
     final proxy = ManagerProxy();
 
-    final incoming = Incoming.fromSvcPath()..connectToService(proxy);
+    final incoming = Incoming.fromSvcPath();
+    if (alternateServiceName != null) {
+      incoming.connectToServiceByNameWithChannel(
+          alternateServiceName, proxy.ctrl.request().passChannel());
+    } else {
+      incoming.connectToService(proxy);
+    }
 
     final id = '${DateTime.now().millisecondsSinceEpoch}';
     final annotations = [
diff --git a/session_shells/ermine/shell/lib/src/services/preferences_service.dart b/session_shells/ermine/shell/lib/src/services/preferences_service.dart
index 2958986..3b4e732 100644
--- a/session_shells/ermine/shell/lib/src/services/preferences_service.dart
+++ b/session_shells/ermine/shell/lib/src/services/preferences_service.dart
@@ -6,6 +6,7 @@
 import 'dart:io';
 
 import 'package:ermine_utils/ermine_utils.dart';
+import 'package:fuchsia_logger/logger.dart';
 import 'package:mobx/mobx.dart';
 
 /// Defines a service that allows reading and storing application data.
@@ -64,12 +65,15 @@
     file = File(kPreferencesJson);
     if (file.existsSync()) {
       result.addAll(parsePreferences(file.readAsStringSync()));
+      log.info('Read settings from previous session');
+    } else {
+      log.info('Failed to read settings from previous session');
     }
 
     return result;
   }
 
   static void _writePreferences(Map<String, dynamic> data) {
-    File(kPreferencesJson).writeAsStringSync(json.encode(data));
+    File(kPreferencesJson).writeAsStringSync(json.encode(data), flush: true);
   }
 }
diff --git a/session_shells/ermine/shell/lib/src/services/presenter_service.dart b/session_shells/ermine/shell/lib/src/services/presenter_service.dart
index b2d74ac..fb83488 100644
--- a/session_shells/ermine/shell/lib/src/services/presenter_service.dart
+++ b/session_shells/ermine/shell/lib/src/services/presenter_service.dart
@@ -13,6 +13,7 @@
 import 'package:fidl/fidl.dart';
 import 'package:fidl_fuchsia_element/fidl_async.dart';
 import 'package:fidl_fuchsia_ui_views/fidl_async.dart';
+import 'package:fuchsia_logger/logger.dart';
 import 'package:fuchsia_scenic_flutter/fuchsia_view.dart';
 import 'package:fuchsia_services/services.dart';
 import 'package:zircon/zircon.dart';
@@ -21,8 +22,7 @@
 typedef ViewDismissedCallback = void Function(ViewState viewState);
 typedef ErrorCallback = void Function(String url, String error);
 
-/// Defines a [GraphicalPresenter] to present and dismiss application views.
-class PresenterService extends GraphicalPresenter {
+class PresenterService {
   late ViewPresentedCallback onViewPresented;
   late ViewDismissedCallback onViewDismissed;
   late VoidCallback onPresenterDisposed;
@@ -30,18 +30,50 @@
 
   PresenterService();
 
+  final _bindings = <_GraphicalPresenterImpl>{};
+
   void advertise(Outgoing outgoing) {
     outgoing.addPublicService(bind, GraphicalPresenter.$serviceName);
   }
 
+  void bind(InterfaceRequest<GraphicalPresenter> request) {
+    final graphicalPresenter = _GraphicalPresenterImpl()
+      ..onPresenterDisposed = onPresenterDisposed
+      ..onViewPresented = onViewPresented
+      ..onViewDismissed = onViewDismissed
+      ..onError = onError
+      ..bind(request);
+    _bindings.add(graphicalPresenter);
+  }
+
+  void dispose() {
+    for (var binding in _bindings) {
+      binding.dispose();
+    }
+  }
+}
+
+/// Defines a [GraphicalPresenter] to present and dismiss application views.
+class _GraphicalPresenterImpl extends GraphicalPresenter {
+  late ViewPresentedCallback onViewPresented;
+  late ViewDismissedCallback onViewDismissed;
+  late VoidCallback onPresenterDisposed;
+  late ErrorCallback onError;
+
+  _GraphicalPresenterImpl();
+
   @override
   Future<void> presentView(
       ViewSpec viewSpec,
       InterfaceHandle<AnnotationController>? annotationController,
       InterfaceRequest<ViewController>? viewControllerRequest) async {
     late ViewStateImpl viewState;
-    final viewController = _ViewControllerImpl(() => onViewDismissed(viewState))
-      ..bind(viewControllerRequest);
+
+    _ViewControllerImpl? viewController;
+    if (viewControllerRequest != null) {
+      viewController = _ViewControllerImpl(() => onViewDismissed(viewState))
+        ..bind(viewControllerRequest);
+    }
 
     // Check to see if we have an id that we included in the annotation.
     final id = _getAnnotation(viewSpec.annotations, 'id') ??
@@ -61,7 +93,7 @@
 
     final viewRef = viewSpec.viewRef;
     if (!_validateViewSpec(viewSpec, url)) {
-      viewController.close();
+      viewController?.close();
       throw MethodException(PresentViewError.invalidArgs);
     }
 
@@ -95,7 +127,7 @@
       id: id,
       title: title!,
       url: url,
-      onClose: viewController.close,
+      onClose: viewController?.close ?? () {},
     );
     onViewPresented(viewState);
   }
@@ -137,6 +169,7 @@
     if ((viewSpec.viewHolderToken == null &&
             viewSpec.viewportCreationToken == null) ||
         viewSpec.viewRef == null) {
+      log.warning('ViewSpec has null ViewportCreationToken, ViewHolderToken or ViewRef');
       if (url != null) {
         onError(url,
             'ViewSpec has null ViewportCreationToken, ViewHolderToken or ViewRef');
@@ -145,7 +178,8 @@
     }
 
     if (viewSpec.viewportCreationToken != null &&
-        viewSpec.viewHolderToken != null) {
+      viewSpec.viewHolderToken != null) {
+      log.warning('ViewSpec has both ViewportCreationToken and ViewHolderToken set');
       if (url != null) {
         onError(url,
             'ViewSpec has both ViewportCreationToken and ViewHolderToken set');
diff --git a/session_shells/ermine/shell/lib/src/services/settings/battery_watcher_service.dart b/session_shells/ermine/shell/lib/src/services/settings/battery_watcher_service.dart
index 537a790..0a7e98b 100644
--- a/session_shells/ermine/shell/lib/src/services/settings/battery_watcher_service.dart
+++ b/session_shells/ermine/shell/lib/src/services/settings/battery_watcher_service.dart
@@ -3,7 +3,7 @@
 // found in the LICENSE file.
 
 import 'package:ermine/src/services/settings/task_service.dart';
-import 'package:fidl_fuchsia_power/fidl_async.dart';
+import 'package:fidl_fuchsia_power_battery/fidl_async.dart';
 import 'package:flutter/material.dart';
 import 'package:fuchsia_services/services.dart';
 
diff --git a/session_shells/ermine/shell/lib/src/services/settings/volume_service.dart b/session_shells/ermine/shell/lib/src/services/settings/volume_service.dart
index 666518f..b9b17e8 100644
--- a/session_shells/ermine/shell/lib/src/services/settings/volume_service.dart
+++ b/session_shells/ermine/shell/lib/src/services/settings/volume_service.dart
@@ -55,6 +55,11 @@
     volume = (volume - 0.1).clamp(0, 1);
   }
 
+  // Toggles mute for volume on/off.
+  void toggleMute() {
+    muted = !muted;
+  }
+
   IconData get icon => _muted ? Icons.volume_off : Icons.volume_up;
 
   bool get muted => _muted;
diff --git a/session_shells/ermine/shell/lib/src/services/settings/wifi_service.dart b/session_shells/ermine/shell/lib/src/services/settings/wifi_service.dart
index 50606df..c7426c0 100644
--- a/session_shells/ermine/shell/lib/src/services/settings/wifi_service.dart
+++ b/session_shells/ermine/shell/lib/src/services/settings/wifi_service.dart
@@ -28,12 +28,16 @@
   StreamSubscription? _connectToWPA2NetworkSubscription;
   StreamSubscription? _savedNetworksSubscription;
   StreamSubscription? _removeNetworkSubscription;
+  StreamSubscription? _startClientConnectionsSubscription;
+  StreamSubscription? _stopClientConnectionsSubscription;
 
   Timer? _timer;
   int scanIntervalInSeconds = 20;
   final _scannedNetworks = <policy.ScanResult>{};
-  String _targetNetwork = '';
+  NetworkInformation _targetNetwork = NetworkInformation();
   final _savedNetworks = <policy.NetworkConfig>{};
+  bool _clientConnectionsEnabled = false;
+  final _networksWithFailedCredentials = <policy.NetworkConfig>{};
 
   WiFiService();
 
@@ -41,7 +45,8 @@
   Future<void> start() async {
     _clientProvider = policy.ClientProviderProxy();
     _clientController = policy.ClientControllerProxy();
-    _monitor = ClientStateUpdatesMonitor(onChanged);
+    _monitor = ClientStateUpdatesMonitor(
+        onChanged, _pollNetworksWithFailedCredentials);
 
     Incoming.fromSvcPath().connectToService(_clientProvider);
 
@@ -49,11 +54,7 @@
         InterfaceRequest(_clientController?.ctrl.request().passChannel()),
         _monitor.getInterfaceHandle());
 
-    final requestStatus = await _clientController?.startClientConnections();
-    if (requestStatus != RequestStatus.acknowledged) {
-      log.warning(
-          'Failed to start wlan client connection. Request status: $requestStatus');
-    }
+    clientConnectionsEnabled = true;
 
     await getSavedNetworks();
 
@@ -68,6 +69,8 @@
     await _connectToWPA2NetworkSubscription?.cancel();
     await _savedNetworksSubscription?.cancel();
     await _removeNetworkSubscription?.cancel();
+    await _startClientConnectionsSubscription?.cancel();
+    await _stopClientConnectionsSubscription?.cancel();
     dispose();
   }
 
@@ -81,29 +84,71 @@
     _scanResultIteratorProvider = policy.ScanResultIteratorProxy();
   }
 
-  String get targetNetwork => _targetNetwork;
-  set targetNetwork(String network) {
+  NetworkInformation get targetNetwork => _targetNetwork;
+  set targetNetwork(NetworkInformation network) {
     _targetNetwork = network;
     onChanged();
   }
 
+  bool get clientConnectionsEnabled => _clientConnectionsEnabled;
+  set clientConnectionsEnabled(bool enabled) {
+    _clientConnectionsEnabled = enabled;
+    if (enabled) {
+      _startClientConnections();
+    } else {
+      _stopClientConnections();
+    }
+    onChanged();
+  }
+
+  Future<void> _startClientConnections() async {
+    if (_stopClientConnectionsSubscription != null) {
+      await _stopClientConnectionsSubscription!.cancel();
+    }
+    _startClientConnectionsSubscription = () async {
+      final requestStatus = await _clientController?.startClientConnections();
+      if (requestStatus != RequestStatus.acknowledged) {
+        log.warning(
+            'Failed to start wlan client connection. Request status: $requestStatus');
+      }
+    }()
+        .asStream()
+        .listen((_) {});
+  }
+
+  Future<void> _stopClientConnections() async {
+    if (_startClientConnectionsSubscription != null) {
+      await _startClientConnectionsSubscription!.cancel();
+    }
+    _stopClientConnectionsSubscription = () async {
+      final requestStatus = await _clientController?.stopClientConnections();
+      if (requestStatus != RequestStatus.acknowledged) {
+        log.warning(
+            'Failed to stop wlan client connection. Request status: $requestStatus');
+      }
+    }()
+        .asStream()
+        .listen((_) {});
+  }
+
   Future<void> scanForNetworks() async {
     _scanForNetworksSubscription = () async {
       _scanResultIteratorProvider = policy.ScanResultIteratorProxy();
       await _clientController?.scanForNetworks(InterfaceRequest(
           _scanResultIteratorProvider?.ctrl.request().passChannel()));
 
-      _scannedNetworks.clear();
       List<policy.ScanResult>? scanResults;
       try {
         scanResults = await _scanResultIteratorProvider?.getNext();
+        _scannedNetworks.clear();
         while (scanResults != null && scanResults.isNotEmpty) {
           _scannedNetworks.addAll(scanResults);
           scanResults = await _scanResultIteratorProvider?.getNext();
         }
       } on Exception catch (e) {
         log.warning('Error encountered during scan: $e');
-        return;
+        // TODO(cwhitten): uncomment once fxb/87664 fixed
+        // return;
       }
       onChanged();
     }()
@@ -111,34 +156,35 @@
         .listen((_) {});
   }
 
-  List<NetworkInformation> get scannedNetworks => _scannedNetworks
-      .map((network) => NetworkInformation(
-          name: nameFromScannedNetwork(network),
-          compatible: compatibleFromScannedNetwork(network),
-          icon: iconFromScannedNetwork(network)))
-      .toList();
+  // TODO(cwhitten): simplify to _scannedNetworks.map(NetworkInformation.fromScanResult).toList();
+  // once passing named contructors is supported by dart.
+  List<NetworkInformation> get scannedNetworks =>
+      networkInformationFromScannedNetworks(_scannedNetworks);
 
-  String nameFromScannedNetwork(policy.ScanResult network) {
-    return utf8.decode(network.id!.ssid.toList());
+  List<NetworkInformation> networkInformationFromScannedNetworks(
+      Set<policy.ScanResult> networks) {
+    var networkInformationList = <NetworkInformation>[];
+    for (var network in networks) {
+      networkInformationList.add(NetworkInformation.fromScanResult(network));
+    }
+    return networkInformationList;
   }
 
-  IconData iconFromScannedNetwork(policy.ScanResult network) {
-    return network.id!.type == policy.SecurityType.none
-        ? Icons.signal_wifi_4_bar
-        : Icons.wifi_lock;
+  Uint8List ssidFromScannedNetwork(policy.ScanResult network) {
+    return network.id!.ssid;
   }
 
-  bool compatibleFromScannedNetwork(policy.ScanResult network) {
-    return network.compatibility == policy.Compatibility.supported;
-  }
-
-  Future<void> connectToWPA2Network(String password) async {
+  Future<void> connectToNetwork(String password) async {
     try {
       _connectToWPA2NetworkSubscription = () async {
-        final utf8password = Uint8List.fromList(password.codeUnits);
-        final credential = policy.Credential.withPassword(utf8password);
+        final credential = _targetNetwork.isOpen
+            ? policy.Credential.withNone(policy.Empty())
+            : policy.Credential.withPassword(
+                Uint8List.fromList(password.codeUnits));
+
         policy.ScanResult? network = _scannedNetworks.firstWhereOrNull(
-            (network) => nameFromScannedNetwork(network) == _targetNetwork);
+            (network) =>
+                ssidFromScannedNetwork(network) == _targetNetwork.ssid);
 
         if (network == null) {
           throw Exception(
@@ -148,7 +194,6 @@
         final networkConfig =
             policy.NetworkConfig(id: network.id, credential: credential);
 
-        // TODO(fxb/79885): Separate save and connect functionality.
         await _clientController?.saveNetwork(networkConfig);
 
         final requestStatus = await _clientController?.connect(network.id!);
@@ -171,7 +216,19 @@
 
   bool get connectionsEnabled => _monitor.connectionsEnabled();
 
-  bool get incorrectPassword => _monitor.incorrectPassword();
+  void _pollNetworksWithFailedCredentials(List<policy.NetworkIdentifier>? ids) {
+    if (ids == null) {
+      return;
+    }
+    for (var id in ids) {
+      final foundNetwork = _savedNetworks.firstWhereOrNull((savedNetwork) =>
+          listEquals(savedNetwork.id?.ssid, id.ssid) &&
+          savedNetwork.id?.type == id.type);
+      if (foundNetwork != null) {
+        _networksWithFailedCredentials.add(foundNetwork);
+      }
+    }
+  }
 
   Future<void> getSavedNetworks() async {
     _savedNetworksSubscription = () async {
@@ -210,6 +267,10 @@
 
         // Refresh list of saved networks
         await getSavedNetworks();
+
+        _networksWithFailedCredentials.removeWhere((networkConfig) =>
+            listEquals(networkConfig.id?.ssid, foundNetwork.id?.ssid) &&
+            networkConfig.id?.type == foundNetwork.id?.type);
       }()
           .asStream()
           .listen((_) {});
@@ -218,21 +279,19 @@
     }
   }
 
-  List<NetworkInformation> get savedNetworks => _savedNetworks
-      .map((network) => NetworkInformation(
-          name: nameFromNetworkConfig(network),
-          compatible: true,
-          icon: iconFromNetworkConfig(network)))
-      .toList();
+  // TODO(cwhitten): simplify to _savedNetworks.map(NetworkInformation.fromNetworkConfig).toList();
+  // once passing named contructors is supported by dart.
+  List<NetworkInformation> get savedNetworks =>
+      networkInformationFromSavedNetworks(_savedNetworks);
 
-  String nameFromNetworkConfig(policy.NetworkConfig network) {
-    return utf8.decode(network.id!.ssid.toList());
-  }
-
-  IconData iconFromNetworkConfig(policy.NetworkConfig network) {
-    return network.id!.type == policy.SecurityType.none
-        ? Icons.signal_wifi_4_bar
-        : Icons.wifi_lock;
+  List<NetworkInformation> networkInformationFromSavedNetworks(
+      Set<policy.NetworkConfig> networks) {
+    var networkInformationList = <NetworkInformation>[];
+    for (var network in networks) {
+      networkInformationList.add(NetworkInformation.fromNetworkConfig(
+          network, _networksWithFailedCredentials));
+    }
+    return networkInformationList;
   }
 }
 
@@ -240,8 +299,11 @@
   final _binding = policy.ClientStateUpdatesBinding();
   policy.ClientStateSummary? _summary;
   late final VoidCallback _onChanged;
+  late final void Function(List<policy.NetworkIdentifier>?)
+      _pollNetworksWithFailedCredentials;
 
-  ClientStateUpdatesMonitor(this._onChanged);
+  ClientStateUpdatesMonitor(
+      this._onChanged, this._pollNetworksWithFailedCredentials);
 
   InterfaceHandle<policy.ClientStateUpdates> getInterfaceHandle() =>
       _binding.wrap(this);
@@ -263,28 +325,93 @@
     return foundNetwork == null ? '' : utf8.decode(foundNetwork);
   }
 
-  // TODO(fxb/79855): ensure that failed password status is for target network
-  bool incorrectPassword() {
-    return _summary?.networks?.firstWhereOrNull((network) =>
-            network.status == policy.DisconnectStatus.credentialsFailed) !=
-        null;
+  // Check for failed credentials and poll networks with failed credentials
+  void _checkForFailedCredentials() {
+    var foundNetworks = _summary?.networks?.where((network) =>
+        network.status == policy.DisconnectStatus.credentialsFailed);
+    var networkIDs = foundNetworks?.map((network) => network.id!).toList();
+    if (networkIDs != null && networkIDs.isNotEmpty) {
+      _pollNetworksWithFailedCredentials(networkIDs);
+    }
   }
 
   @override
   Future<void> onClientStateUpdate(policy.ClientStateSummary summary) async {
     _summary = summary;
+    _checkForFailedCredentials();
     _onChanged();
   }
 }
 
 /// Network information needed for UI
 class NetworkInformation {
-  String name;
-  bool compatible;
-  IconData icon;
+  // SSID used for lookup
+  Uint8List? _ssid;
+  // String representation of SSID in UI
+  String? _name;
+  // If network is able to be connected to
+  bool _compatible = false;
+  // Security type of network
+  policy.SecurityType? _securityType;
+  // If network has a failed connection attempt due to bad credentials
+  // Only set true if failed credentials found
+  bool credentialsFailed = false;
 
-  NetworkInformation(
-      {this.name = '',
-      this.compatible = false,
-      this.icon = Icons.signal_wifi_4_bar});
+  NetworkInformation();
+
+  // Constructor for network config
+  NetworkInformation.fromNetworkConfig(policy.NetworkConfig networkConfig,
+      [Set<policy.NetworkConfig> networksWithFailedCredentials = const {}]) {
+    _ssid = networkConfig.id?.ssid;
+    _name = networkConfig.id?.ssid.toList() != null
+        ? utf8.decode(networkConfig.id!.ssid.toList())
+        : null;
+    _compatible = true;
+    _securityType = networkConfig.id?.type;
+    if (networksWithFailedCredentials.isNotEmpty) {
+      if (networksWithFailedCredentials
+              .map((networkConfig) => networkConfig.id!)
+              .firstWhereOrNull((networkIdentifier) =>
+                  listEquals(networkIdentifier.ssid, _ssid) &&
+                  networkIdentifier.type == _securityType) !=
+          null) {
+        credentialsFailed = true;
+      }
+    }
+  }
+
+  // Constructor for scan result
+  NetworkInformation.fromScanResult(policy.ScanResult scanResult) {
+    _ssid = scanResult.id?.ssid;
+    _name = scanResult.id?.ssid.toList() != null
+        ? utf8.decode(scanResult.id!.ssid.toList())
+        : null;
+    // Only allow valid characters in UI representation of SSID
+    // TODO(fxb/92668): Allow special characters, such as emojis, in network names
+    if (_name != null) {
+      _name = _name!.replaceAll(RegExp(r'[^A-Za-z0-9()\[\]\s+.,;?&_-]'), '');
+    }
+    _compatible = scanResult.compatibility == policy.Compatibility.supported;
+    _securityType = scanResult.id?.type;
+  }
+
+  Uint8List? get ssid => _ssid;
+
+  String get name => _name ?? '';
+
+  bool get compatible => _compatible;
+
+  IconData get icon => _securityType == policy.SecurityType.none
+      ? Icons.signal_wifi_4_bar
+      : Icons.wifi_lock;
+
+  bool get isOpen => _securityType == policy.SecurityType.none;
+
+  bool get isWEP => _securityType == policy.SecurityType.wep;
+
+  bool get isWPA => _securityType == policy.SecurityType.wpa;
+
+  bool get isWPA2 => _securityType == policy.SecurityType.wpa2;
+
+  bool get isWPA3 => _securityType == policy.SecurityType.wpa3;
 }
diff --git a/session_shells/ermine/shell/lib/src/services/shortcuts_service.dart b/session_shells/ermine/shell/lib/src/services/shortcuts_service.dart
index 645bffb..3984bc7 100644
--- a/session_shells/ermine/shell/lib/src/services/shortcuts_service.dart
+++ b/session_shells/ermine/shell/lib/src/services/shortcuts_service.dart
@@ -6,6 +6,7 @@
 import 'dart:io';
 
 import 'package:fidl_fuchsia_ui_views/fidl_async.dart';
+import 'package:fuchsia_logger/logger.dart';
 import 'package:internationalization/strings.dart';
 import 'package:keyboard_shortcuts/keyboard_shortcuts.dart';
 
@@ -17,6 +18,9 @@
 
   final ViewRef hostViewRef;
 
+  /// Returns the last shortcut received by the application.
+  String lastShortcutAction = '';
+
   ShortcutsService(this.hostViewRef);
 
   late final KeyboardShortcuts _keyboardShortcuts;
@@ -30,12 +34,17 @@
 
     _keyboardShortcuts = KeyboardShortcuts.withViewRef(
       hostViewRef,
-      actions: actions.map((k, v) => MapEntry(k, () => v())),
+      actions: actions.map((k, v) => MapEntry(k, () {
+            log.info('Received shortcut action: $k');
+            lastShortcutAction = k;
+            v();
+          })),
       bindings: bindings,
     );
 
     // Hook up actions to flutter driver handler.
     flutterDriverHandler = (command) async {
+      log.info('Received flutter driver command: $command');
       return actions[command]?.call();
     };
   }
diff --git a/session_shells/ermine/shell/lib/src/services/startup_service.dart b/session_shells/ermine/shell/lib/src/services/startup_service.dart
index 9e8b747..96c474b 100644
--- a/session_shells/ermine/shell/lib/src/services/startup_service.dart
+++ b/session_shells/ermine/shell/lib/src/services/startup_service.dart
@@ -5,10 +5,11 @@
 import 'dart:async';
 import 'dart:convert' show json;
 import 'dart:io';
+import 'dart:isolate';
 
 import 'package:ermine_utils/ermine_utils.dart';
 import 'package:fidl_fuchsia_buildinfo/fidl_async.dart' as buildinfo;
-import 'package:fidl_fuchsia_device_manager/fidl_async.dart';
+import 'package:fidl_fuchsia_hardware_power_statecontrol/fidl_async.dart';
 import 'package:fidl_fuchsia_intl/fidl_async.dart';
 import 'package:fidl_fuchsia_ui_activity/fidl_async.dart' as activity;
 import 'package:flutter/services.dart';
@@ -68,7 +69,7 @@
 
   final _inspect = Inspect();
   final _intl = PropertyProviderProxy();
-  final _deviceManager = AdministratorProxy();
+  final _hardwareAdmin = AdminProxy();
   final _provider = buildinfo.ProviderProxy();
   final _activity = activity.ProviderProxy();
   final _activityBinding = activity.ListenerBinding();
@@ -79,8 +80,8 @@
   StartupService()
       : componentContext = ComponentContext.create(),
         hostView = ViewHandle(ScenicContext.hostViewRef()) {
+    Incoming.fromSvcPath().connectToService(_hardwareAdmin);
     Incoming.fromSvcPath().connectToService(_intl);
-    Incoming.fromSvcPath().connectToService(_deviceManager);
     Incoming.fromSvcPath().connectToService(_provider);
     Incoming.fromSvcPath().connectToService(_activity);
     Incoming.fromSvcPath().connectToService(_activityTracker);
@@ -95,15 +96,19 @@
       RawKeyboard.instance.addListener((event) {
         // We use Alt key release event to dismiss app switching UI.
         final data = event.data as RawKeyEventDataFuchsia;
-        final fuchsiaKey = FuchsiaKeyboard.kHidUsagePageMask | data.hidUsage;
+        final fuchsiaKey = data.hidUsage;
         if (fuchsiaKey == PhysicalKeyboardKey.altLeft.usbHidUsage ||
             fuchsiaKey == PhysicalKeyboardKey.altRight.usbHidUsage) {
           if (!event.isAltPressed) {
             onAltReleased();
           }
         }
-        // Notify activity service to user input.
-        onActivity('keyboard');
+        // Notify activity service of user input. This is used to dismiss the
+        // screen saver if it is active. We do this only for key presses
+        // because key release from screensaver shortcut itself might cancel it.
+        if (event is RawKeyDownEvent) {
+          onActivity('keyboard');
+        }
       });
     });
 
@@ -153,7 +158,7 @@
   }
 
   void dispose() {
-    _deviceManager.ctrl.close();
+    _hardwareAdmin.ctrl.close();
     _intl.ctrl.close();
     _provider.ctrl.close();
     _activityBinding.close();
@@ -197,10 +202,20 @@
   String get buildVersion => _buildVersion;
 
   /// Reboot the device.
-  void restartDevice() => _deviceManager.suspend(suspendFlagReboot);
+  void restartDevice() => _hardwareAdmin.reboot(RebootReason.userRequest);
 
   /// Shutdown the device.
-  void shutdownDevice() => _deviceManager.suspend(suspendFlagPoweroff);
+  void shutdownDevice() => _hardwareAdmin.poweroff();
+
+  /// Logout of the user shell.
+  void logout() {
+    // Exit the current isolate, which allows the parent to treat it as a logout
+    // action.
+    WidgetsBinding.instance.addPostFrameCallback((_) {
+      Isolate.current.setErrorsFatal(true);
+      Isolate.current.kill(priority: Isolate.beforeNextEvent);
+    });
+  }
 
   Stream<Locale> get stream => LocaleSource(_intl).stream();
 
diff --git a/session_shells/ermine/shell/lib/src/states/app_state.dart b/session_shells/ermine/shell/lib/src/states/app_state.dart
index 4e3796c..6de8e3c 100644
--- a/session_shells/ermine/shell/lib/src/states/app_state.dart
+++ b/session_shells/ermine/shell/lib/src/states/app_state.dart
@@ -12,7 +12,6 @@
 import 'package:ermine/src/states/app_state_impl.dart';
 import 'package:ermine/src/states/settings_state.dart';
 import 'package:ermine/src/states/view_state.dart';
-import 'package:ermine/src/widgets/app_bar.dart';
 import 'package:ermine/src/widgets/dialogs/dialog.dart' as ermine;
 import 'package:flutter/material.dart' hide Action, AppBar;
 import 'package:fuchsia_scenic/views.dart';
@@ -36,7 +35,7 @@
   bool get viewsVisible;
   ViewState get topView;
   ViewState? get switchTarget;
-  List<ermine.Dialog> get dialogs;
+  List<ermine.DialogInfo> get dialogs;
   List<ViewState> get views;
   Map<String, List<String>> get errors;
   Locale? get locale;
@@ -54,10 +53,11 @@
   void switchView(ViewState view);
   void cancel();
   void closeView();
-  void launch(String title, String url);
+  void launch(String title, String url, {String? alternateServiceName});
   void setTheme({bool darkTheme});
   void restart();
   void shutdown();
+  void logout();
   void launchFeedback();
   void launchLicense();
   void checkingForUpdatesAlert();
@@ -72,7 +72,7 @@
       preferencesService: PreferencesService(),
       pointerEventsService: PointerEventsService(
         ScenicContext.hostViewRef(),
-        insets: EdgeInsets.only(left: AppBar.kWidth),
+        insets: EdgeInsets.zero,
       ),
     ) as AppState;
   }
diff --git a/session_shells/ermine/shell/lib/src/states/app_state_impl.dart b/session_shells/ermine/shell/lib/src/states/app_state_impl.dart
index 07830f8..0b441af 100644
--- a/session_shells/ermine/shell/lib/src/states/app_state_impl.dart
+++ b/session_shells/ermine/shell/lib/src/states/app_state_impl.dart
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// ignore_for_file: unnecessary_lambdas
 import 'dart:convert';
 
 import 'package:ermine/src/services/focus_service.dart';
@@ -15,8 +16,7 @@
 import 'package:ermine/src/states/settings_state.dart';
 import 'package:ermine/src/states/view_state.dart';
 import 'package:ermine/src/states/view_state_impl.dart';
-import 'package:ermine/src/widgets/dialogs/dialog.dart' as ermine;
-import 'package:ermine/src/widgets/dialogs/text_only_dialog.dart';
+import 'package:ermine/src/widgets/dialogs/dialog.dart';
 import 'package:ermine_utils/ermine_utils.dart';
 import 'package:flutter/material.dart' hide Action;
 import 'package:fuchsia_inspect/inspect.dart';
@@ -62,7 +62,8 @@
 
     // Register keyboard shortcuts and then initialize SettingsState with it.
     shortcutsService.register(_actions);
-    settingsState = SettingsState.from(shortcutsService: shortcutsService);
+    settingsState =
+        SettingsState.from(shortcutBindings: shortcutsService.keyboardBindings);
 
     pointerEventsService
       ..onPeekBegin = _onPeekBegin
@@ -173,7 +174,7 @@
   }).asComputed();
 
   @override
-  final dialogs = <ermine.Dialog>[].asObservable();
+  final dialogs = <DialogInfo>[].asObservable();
 
   @override
   final errors = <String, List<String>>{}.asObservable();
@@ -306,7 +307,18 @@
   }
 
   @override
-  void cancel() => hideOverlay();
+  void cancel() {
+    if (!dialogsVisible) {
+      hideOverlay();
+    }
+    // If top view is a screensaver, dismiss it.
+    // TODO(fxb/80131): Use cancel action associated with Esc keyboard shortcut
+    // to dismiss the screensaver, since mouse and keyboard input is not
+    // available to the shell when a child view is fullscreen.
+    if (views.isNotEmpty && topView.url == kScreenSaverUrl) {
+      _onIdle(idle: false);
+    }
+  }
 
   @override
   void closeView() => _closeView();
@@ -326,11 +338,14 @@
   }.asAction();
 
   @override
-  void launch(String title, String url) => _launch([title, url]);
-  late final _launch = (String title, String url) async {
+  void launch(String title, String url, {String? alternateServiceName}) =>
+      _launch([title, url, alternateServiceName]);
+  late final _launch =
+      (String title, String url, String? alternateServiceName) async {
     try {
       _clearError(url, 'ProposeElementError');
-      await launchService.launch(title, url);
+      await launchService.launch(title, url,
+          alternateServiceName: alternateServiceName);
       // Hide app launcher unless we had an error presenting the view.
       if (!_isLaunchError(url)) {
         runInAction(() {
@@ -357,31 +372,67 @@
   }.asAction();
 
   @override
-  void restart() => runInAction(startupService.restartDevice);
+  void restart() {
+    _displayDialog(AlertDialogInfo(
+      title: Strings.confirmRestartAlertTitle,
+      body: Strings.confirmToSaveWorkAlertBody,
+      actions: [Strings.cancel, Strings.restart],
+      defaultAction: Strings.restart,
+      onAction: (action) {
+        if (action == Strings.restart) {
+          startupService.restartDevice();
+          // Clean up.
+          dispose();
+        }
+      },
+    ));
+  }
 
   @override
-  void shutdown() => runInAction(startupService.shutdownDevice);
+  void shutdown() {
+    _displayDialog(AlertDialogInfo(
+      title: Strings.confirmShutdownAlertTitle,
+      body: Strings.confirmToSaveWorkAlertBody,
+      actions: [Strings.cancel, Strings.shutdown],
+      defaultAction: Strings.shutdown,
+      onAction: (action) {
+        if (action == Strings.shutdown) {
+          startupService.shutdownDevice();
+          // Clean up.
+          dispose();
+        }
+      },
+    ));
+  }
+
+  @override
+  void logout() {
+    _displayDialog(AlertDialogInfo(
+      title: Strings.confirmLogoutAlertTitle,
+      body: Strings.confirmToSaveWorkAlertBody,
+      actions: [Strings.cancel, Strings.logout],
+      defaultAction: Strings.logout,
+      onAction: (action) {
+        if (action == Strings.logout) {
+          dispose();
+          startupService.logout();
+        }
+      },
+    ));
+  }
 
   @override
   void checkingForUpdatesAlert() {
-    runInAction(() {
-      final key = Key(
-          'checkingforupdatesalert_${DateTime.now().millisecondsSinceEpoch}');
-      dialogs.add(TextOnlyDialog(
-        key: key,
-        title: Strings.channelUpdateAlertTitle,
-        body: Strings.channelUpdateAlertBody,
-        buttons: {
-          Strings.close: () {
-            dialogs.removeWhere((dialog) => dialog.key == key);
-          },
-          Strings.continueLabel: () {
-            settingsState.checkForUpdates();
-            dialogs.removeWhere((dialog) => dialog.key == key);
-          },
-        },
-      ));
-    });
+    _displayDialog(AlertDialogInfo(
+      title: Strings.channelUpdateAlertTitle,
+      body: Strings.channelUpdateAlertBody,
+      actions: [Strings.close, Strings.continueLabel],
+      onAction: (action) {
+        if (action == Strings.continueLabel) {
+          settingsState.checkForUpdates();
+        }
+      },
+    ));
   }
 
   late final showScreenSaver = () {
@@ -408,10 +459,12 @@
             settingsState.showAllSettings();
           }
         },
-        // ignore: unnecessary_lambdas
         'increaseBrightness': () => settingsState.increaseBrightness(),
-        // ignore: unnecessary_lambdas
         'decreaseBrightness': () => settingsState.decreaseBrightness(),
+        'increaseVolume': () => settingsState.increaseVolume(),
+        'decreaseVolume': () => settingsState.decreaseVolume(),
+        'muteVolume': () => settingsState.toggleMute(),
+        'logout': logout,
       };
 
   final _focusedView = Observable<ViewHandle?>(null);
@@ -464,10 +517,6 @@
       views.add(view);
       topView = view;
 
-      // If the child view is the screen saver, make it non-focusable in order
-      // for keyboard input to get routed to the shell and dismiss it.
-      viewState.focusable = viewState.url != kScreenSaverUrl;
-
       // If any, remove previously cached launch errors for the app.
       if (viewState.url != null) {
         _clearError(viewState.url!, 'ViewControllerEpitaph');
@@ -482,10 +531,11 @@
     }));
 
     // Update view hittestability based on overlay visibility.
-    view.reactions.add(reaction<bool>((_) => overlaysVisible, (overlay) {
+    view.reactions.add(reaction<bool>(
+        (_) => overlaysVisible || switcherVisible || dialogsVisible, (overlay) {
       // Don't reset hittest flag when showing app switcher, because the
       // app switcher does not react to pointer events.
-      view.hitTestable = !overlay || switcherVisible;
+      view.hitTestable = !overlay;
     }));
 
     // Remove view from views when it is closed.
@@ -499,13 +549,13 @@
   void _onViewDismissed(ViewState viewState) {
     runInAction(() {
       final view = viewState as ViewStateImpl;
-      // Switch to next view before closing this view if it was the top view
+      // Switch to previous view before closing this view if it was the top view
       // and there are other views.
       if (view == topView && views.length > 1) {
-        final nextView = topView == views.last
-            ? views.first
-            : views[views.indexOf(topView) + 1];
-        topView = nextView;
+        final prevView = view != views.first
+            ? views[views.indexOf(topView) - 1]
+            : views.last;
+        topView = prevView;
         setFocusToChildView();
       }
 
@@ -541,23 +591,17 @@
               ? Strings.viewPresentRejectedDesc
               : Strings.defaultPresentErrorDesc;
       const referenceLink =
-          'https://fuchsia.dev/reference/fidl/fuchsia.session#ViewControllerEpitaph';
+          'https://fuchsia.dev/reference/fidl/fuchsia.element#GraphicalPresenter.PresentView';
 
       if (_isPrelistedApp(url)) {
         errors[url] = [description, '$error\n$referenceLink'];
       } else {
-        final key = Key('presenterr_${DateTime.now().millisecondsSinceEpoch}');
-        dialogs.add(TextOnlyDialog(
-          key: key,
+        _displayDialog(AlertDialogInfo(
           title: description,
           body: '${Strings.errorWhilePresenting},\n$url\n\n'
               '${Strings.errorType}: $error\n\n'
               '${Strings.moreErrorInformation}\n$referenceLink',
-          buttons: {
-            Strings.close: () {
-              dialogs.removeWhere((dialog) => dialog.key == key);
-            }
-          },
+          actions: [Strings.close],
         ));
       }
     });
@@ -572,10 +616,12 @@
         : errorSpec == 'rejected'
             ? Strings.launchRejectedDesc
             : Strings.defaultProposeErrorDesc;
+    const referenceLink =
+        'https://fuchsia.dev/reference/fidl/fuchsia.element#Manager.ProposeElement';
 
     errors[url] = [
       description,
-      '$proposeError\nhttps://fuchsia.dev/reference/fidl/fuchsia.session#ProposeElementError'
+      '$proposeError\n\n${Strings.moreErrorInformation}\n$referenceLink'
     ];
   }
 
@@ -628,17 +674,11 @@
         return;
       }
       final description = Strings.applicationFailedToStart(view.title);
-      final key = Key('startfail_${DateTime.now().millisecondsSinceEpoch}');
-      dialogs.add(TextOnlyDialog(
-        key: key,
+      _displayDialog(AlertDialogInfo(
         title: description,
         body: 'Url: ${view.url}',
-        buttons: {
-          Strings.close: () {
-            dialogs.removeWhere((dialog) => dialog.key == key);
-            view.close();
-          }
-        },
+        actions: [Strings.close],
+        onClose: view.close,
       ));
     }
   }
@@ -649,6 +689,8 @@
     data['appBarVisible'] = appBarVisible;
     data['sideBarVisible'] = sideBarVisible;
     data['overlaysVisible'] = overlaysVisible;
+    data['lastAction'] = shortcutsService.lastShortcutAction;
+    data['darkMode'] = hasDarkTheme;
 
     // Number of running component views.
     data['numViews'] = views.length;
@@ -668,7 +710,7 @@
         final viewData = data['view-$i'];
         viewData['title'] = view.title;
         viewData['url'] = view.url;
-        viewData['focused'] = view == topView;
+        viewData['focused'] = view.view == _focusedView.value;
 
         final viewport = view.viewport;
         if (viewport != null) {
@@ -684,6 +726,16 @@
     return data;
   }
 
+  void _displayDialog(DialogInfo dialog) {
+    runInAction(() {
+      dialogs.add(dialog);
+      if (viewsVisible) {
+        // Set focus to shell view so that we can receive the esc key press.
+        setFocusToShellView();
+      }
+    });
+  }
+
   // Adds inspect data when requested by [Inspect].
   void _onInspect(Node node, [Map<String, dynamic>? inspectData]) {
     final data = inspectData ?? _getInspectData();
diff --git a/session_shells/ermine/shell/lib/src/states/settings_state.dart b/session_shells/ermine/shell/lib/src/states/settings_state.dart
index 780c213..889c2c0 100644
--- a/session_shells/ermine/shell/lib/src/states/settings_state.dart
+++ b/session_shells/ermine/shell/lib/src/states/settings_state.dart
@@ -12,7 +12,6 @@
 import 'package:ermine/src/services/settings/timezone_service.dart';
 import 'package:ermine/src/services/settings/volume_service.dart';
 import 'package:ermine/src/services/settings/wifi_service.dart';
-import 'package:ermine/src/services/shortcuts_service.dart';
 import 'package:ermine/src/states/settings_state_impl.dart';
 import 'package:ermine/src/widgets/quick_settings.dart';
 import 'package:ermine/src/widgets/settings/setting_details.dart';
@@ -80,12 +79,16 @@
   double? get volumeLevel;
   bool? get volumeMuted;
   List<NetworkInformation> get availableNetworks;
-  String get targetNetwork;
+  NetworkInformation get targetNetwork;
   List<NetworkInformation> get savedNetworks;
+  TextEditingController get networkPasswordTextController;
+  String get currentNetwork;
+  bool get clientConnectionsEnabled;
 
-  factory SettingsState.from({required ShortcutsService shortcutsService}) {
+  factory SettingsState.from(
+      {required Map<String, Set<String>> shortcutBindings}) {
     return SettingsStateImpl(
-      shortcutsService: shortcutsService,
+      shortcutBindings: shortcutBindings,
       timezoneService: TimezoneService(),
       dateTimeService: DateTimeService(),
       networkService: NetworkAddressService(),
@@ -113,7 +116,11 @@
   void setVolumeLevel(double value);
   void setVolumeMute({bool muted});
   void showWiFiSettings();
-  void connectToWPA2Network(String password);
-  void setTargetNetwork(String network);
+  void connectToNetwork([String password]);
+  void setTargetNetwork(NetworkInformation network);
   void removeNetwork(String network);
+  void increaseVolume();
+  void decreaseVolume();
+  void toggleMute();
+  void setClientConnectionsEnabled({bool enabled});
 }
diff --git a/session_shells/ermine/shell/lib/src/states/settings_state_impl.dart b/session_shells/ermine/shell/lib/src/states/settings_state_impl.dart
index dfb7bc1..dd0c779 100644
--- a/session_shells/ermine/shell/lib/src/states/settings_state_impl.dart
+++ b/session_shells/ermine/shell/lib/src/states/settings_state_impl.dart
@@ -14,7 +14,6 @@
 import 'package:ermine/src/services/settings/timezone_service.dart';
 import 'package:ermine/src/services/settings/volume_service.dart';
 import 'package:ermine/src/services/settings/wifi_service.dart';
-import 'package:ermine/src/services/shortcuts_service.dart';
 import 'package:ermine/src/states/settings_state.dart';
 import 'package:ermine_utils/ermine_utils.dart';
 import 'package:flutter/material.dart' hide Action;
@@ -161,9 +160,16 @@
   final Observable<bool?> _volumeMuted = Observable<bool?>(null);
 
   @override
-  String get targetNetwork => _targetNetwork.value;
-  set targetNetwork(String value) => _targetNetwork.value = value;
-  final Observable<String> _targetNetwork = Observable<String>('');
+  NetworkInformation get targetNetwork => _targetNetwork.value;
+  set targetNetwork(NetworkInformation value) => _targetNetwork.value = value;
+  final Observable<NetworkInformation> _targetNetwork =
+      Observable<NetworkInformation>(NetworkInformation());
+
+  @override
+  TextEditingController get networkPasswordTextController =>
+      _networkPasswordTextController;
+  final TextEditingController _networkPasswordTextController =
+      TextEditingController();
 
   @override
   final List<NetworkInformation> availableNetworks =
@@ -173,6 +179,17 @@
   final List<NetworkInformation> savedNetworks =
       ObservableList<NetworkInformation>();
 
+  @override
+  String get currentNetwork => _currentNetwork.value;
+  set currentNetwork(String value) => _currentNetwork.value = value;
+  final Observable<String> _currentNetwork = ''.asObservable();
+
+  @override
+  bool get clientConnectionsEnabled => _clientConnectionsEnabled.value;
+  set clientConnectionsEnabled(bool value) =>
+      _clientConnectionsEnabled.value = value;
+  final Observable<bool> _clientConnectionsEnabled = Observable<bool>(true);
+
   final List<String> _timezones;
 
   @override
@@ -199,7 +216,7 @@
   final WiFiService wifiService;
 
   SettingsStateImpl({
-    required ShortcutsService shortcutsService,
+    required this.shortcutBindings,
     required this.timezoneService,
     required this.dateTimeService,
     required this.networkService,
@@ -209,8 +226,7 @@
     required this.channelService,
     required this.volumeService,
     required this.wifiService,
-  })  : shortcutBindings = shortcutsService.keyboardBindings,
-        _timezones = _loadTimezones(),
+  })  : _timezones = _loadTimezones(),
         _selectedTimezone = timezoneService.timezone.asObservable() {
     dateTimeService.onChanged = updateDateTime;
     timezoneService.onChanged =
@@ -310,6 +326,8 @@
           ..clear()
           ..addAll(wifiService.savedNetworks)
           ..removeWhere((network) => network.name.isEmpty);
+        currentNetwork = wifiService.currentNetwork;
+        clientConnectionsEnabled = wifiService.clientConnectionsEnabled;
       });
     };
   }
@@ -430,14 +448,27 @@
       runInAction(() => settingsPage.value = SettingsPage.wifi);
 
   @override
-  void connectToWPA2Network(String password) =>
-      runInAction(() => wifiService.connectToWPA2Network(password));
+  void connectToNetwork([String password = '']) =>
+      runInAction(() => wifiService.connectToNetwork(password));
 
   @override
-  void setTargetNetwork(String network) =>
+  void setTargetNetwork(NetworkInformation network) =>
       runInAction(() => wifiService.targetNetwork = network);
 
   @override
   void removeNetwork(String network) =>
       runInAction(() => wifiService.remove(network));
+
+  @override
+  void increaseVolume() => runInAction(volumeService.increaseVolume);
+
+  @override
+  void decreaseVolume() => runInAction(volumeService.decreaseVolume);
+
+  @override
+  void toggleMute() => runInAction(volumeService.toggleMute);
+
+  @override
+  void setClientConnectionsEnabled({bool enabled = true}) =>
+      runInAction(() => wifiService.clientConnectionsEnabled = enabled);
 }
diff --git a/session_shells/ermine/shell/lib/src/states/view_state_impl.dart b/session_shells/ermine/shell/lib/src/states/view_state_impl.dart
index 7073d24..d9c9efd 100644
--- a/session_shells/ermine/shell/lib/src/states/view_state_impl.dart
+++ b/session_shells/ermine/shell/lib/src/states/view_state_impl.dart
@@ -131,7 +131,10 @@
     int retry = _kMaxRetries,
     Duration backOff = _kBackoffDuration,
   }) {
-    if (loaded && !visible) {
+    // Flatland does not send viewStateChanged events per frame, so setting
+    // _connected to false would be a one way transition and would prevent
+    // this view from ever becoming focusable again.
+    if (!viewConnection.useFlatland && (loaded && !visible)) {
       // Reset connected flag to retry setFocus on next viewStateChanged.
       _connected.value = false;
     } else {
@@ -139,7 +142,7 @@
         return;
       }
       _requestFocusPending = true;
-      FocusState.instance.requestFocus(view.handle).then((_) {
+      viewConnection.requestFocus().then((_) {
         _requestFocusPending = false;
       }).catchError((e) {
         if (retry > 0) {
diff --git a/session_shells/ermine/shell/lib/src/widgets/app.dart b/session_shells/ermine/shell/lib/src/widgets/app.dart
index 2da77d9..4244477 100644
--- a/session_shells/ermine/shell/lib/src/widgets/app.dart
+++ b/session_shells/ermine/shell/lib/src/widgets/app.dart
@@ -6,6 +6,7 @@
 
 import 'package:ermine/src/states/app_state.dart';
 import 'package:ermine/src/widgets/app_view.dart';
+import 'package:ermine/src/widgets/dialogs/dialogs.dart';
 import 'package:ermine/src/widgets/overlays.dart';
 import 'package:ermine_utils/ermine_utils.dart';
 import 'package:flutter/material.dart' hide AppBar;
@@ -37,11 +38,10 @@
         locale: locale,
         localizationsDelegates: [
           localizations.delegate(),
-          GlobalMaterialLocalizations.delegate,
+          ...GlobalMaterialLocalizations.delegates,
           GlobalWidgetsLocalizations.delegate,
         ],
         supportedLocales: supported_locales.locales,
-        shortcuts: FuchsiaKeyboard.defaultShortcuts,
         scrollBehavior: MaterialScrollBehavior().copyWith(
           dragDevices: {PointerDeviceKind.mouse, PointerDeviceKind.touch},
         ),
@@ -61,6 +61,10 @@
                   // Show scrim and overlay layers if an overlay is visible.
                   if (app.overlaysVisible)
                     WidgetFactory.create(() => Overlays(app)),
+
+                  // Show dialogs above all.
+                  if (app.dialogsVisible)
+                    WidgetFactory.create(() => Dialogs(app)),
                 ],
               );
             }),
diff --git a/session_shells/ermine/shell/lib/src/widgets/app_launcher.dart b/session_shells/ermine/shell/lib/src/widgets/app_launcher.dart
index 35eb917..263df3b 100644
--- a/session_shells/ermine/shell/lib/src/widgets/app_launcher.dart
+++ b/session_shells/ermine/shell/lib/src/widgets/app_launcher.dart
@@ -58,7 +58,8 @@
               enabled: _isEnabled(item),
               onTap: () {
                 if (!_isLoading(item)) {
-                  app.launch(item['title']!, item['url']!);
+                  app.launch(item['title']!, item['url']!,
+                      alternateServiceName: item['element_manager_name']);
                 }
               },
             );
diff --git a/session_shells/ermine/shell/lib/src/widgets/dialogs/dialog.dart b/session_shells/ermine/shell/lib/src/widgets/dialogs/dialog.dart
index d207602..93ed72b 100644
--- a/session_shells/ermine/shell/lib/src/widgets/dialogs/dialog.dart
+++ b/session_shells/ermine/shell/lib/src/widgets/dialogs/dialog.dart
@@ -2,10 +2,81 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import 'package:flutter/material.dart';
+/// An base class for information for aler dialogs.
+class DialogInfo {
+  /// The title of the alert dialog box.
+  final String? title;
 
-/// An abstract class for all Dialog widgets that will be carried by
-/// [AppState.dialogs].
-abstract class Dialog extends StatelessWidget {
-  const Dialog({Key? key}) : super(key: key);
+  /// Optional. The default action to invoke if the user presses submit button.
+  /// This action MUST be present in the list of [actions].
+  final String? defaultAction;
+
+  /// The list of actions that are presented as buttons. The default action is
+  /// drawn using [ElevatedButton] to emphasize it's default nature.
+  final List<String> actions;
+
+  /// Optional. Callback when the dialog is closed.
+  final void Function()? onClose;
+
+  /// Optional. Callback when a specific action is invoked.
+  final void Function(String action)? onAction;
+
+  const DialogInfo({
+    required this.actions,
+    this.defaultAction,
+    this.onAction,
+    this.onClose,
+    this.title,
+  });
+}
+
+/// A class for holding information for alert dialogs widgets that will be
+/// carried by [AppState.dialogs].
+class AlertDialogInfo extends DialogInfo {
+  /// Optional. The content body of the dialog box.
+  final String? body;
+
+  const AlertDialogInfo({
+    required String title,
+    required List<String> actions,
+    String? defaultAction,
+    void Function(String action)? onAction,
+    void Function()? onClose,
+    this.body,
+  }) : super(
+          title: title,
+          actions: actions,
+          defaultAction: defaultAction,
+          onAction: onAction,
+          onClose: onClose,
+        );
+}
+
+/// A class for holding information for password capture dialogs widgets that
+/// will be carried by [AppState.dialogs].
+class PasswordDialogInfo extends DialogInfo {
+  /// The password prompt to show above the password text field.
+  final String prompt;
+
+  /// The callback to receive the entered password.
+  final void Function(String? password) onSubmit;
+
+  /// Optional. Callback to validate the password. Returns an error text on
+  /// validation fail or [null] for success.
+  final String? Function(String? password)? validator;
+
+  PasswordDialogInfo({
+    required this.prompt,
+    required this.onSubmit,
+    required List<String> actions,
+    String? defaultAction,
+    void Function(String action)? onAction,
+    void Function()? onClose,
+    this.validator,
+  }) : super(
+          actions: actions,
+          defaultAction: defaultAction,
+          onAction: onAction,
+          onClose: onClose,
+        );
 }
diff --git a/session_shells/ermine/shell/lib/src/widgets/dialogs/dialogs.dart b/session_shells/ermine/shell/lib/src/widgets/dialogs/dialogs.dart
index 00c9944..36bd170 100644
--- a/session_shells/ermine/shell/lib/src/widgets/dialogs/dialogs.dart
+++ b/session_shells/ermine/shell/lib/src/widgets/dialogs/dialogs.dart
@@ -3,23 +3,104 @@
 // found in the LICENSE file.
 
 import 'package:ermine/src/states/app_state.dart';
+import 'package:ermine/src/widgets/dialogs/dialog.dart';
+import 'package:ermine/src/widgets/dialogs/password_prompt.dart';
 import 'package:flutter/material.dart';
-import 'package:flutter_mobx/flutter_mobx.dart';
+import 'package:mobx/mobx.dart';
 
-/// A stack of [Dialog] widgets.
-///
-/// The last added dialog comes to the top of the stack.
+/// Displays dialogs sequentially.
 class Dialogs extends StatelessWidget {
-  final AppState _app;
+  final AppState app;
 
-  const Dialogs(this._app);
+  const Dialogs(this.app);
 
   @override
-  Widget build(BuildContext context) => RepaintBoundary(child: Observer(
+  Widget build(BuildContext context) {
+    // Display queued up dialogs if none are being displayed currently.
+    if (!Navigator.canPop(context)) {
+      WidgetsBinding.instance.addPostFrameCallback((_) {
+        _showAllDialogs(context);
+      });
+    }
+    return Offstage();
+  }
+
+  // Shows all dialogs sequentially.
+  void _showAllDialogs(BuildContext context, [int index = 0]) async {
+    if (index >= app.dialogs.length) {
+      runInAction(() {
+        app.dialogs.clear();
+        // Hiding overlays should restore focus to child views.
+        app.hideOverlay();
+      });
+      return;
+    }
+    final _formState = GlobalKey<FormState>();
+    bool validate() => _formState.currentState?.validate() ?? false;
+
+    final dialog = app.dialogs[index];
+    final result = await showDialog<String?>(
+        context: context,
         builder: (context) {
-          return Stack(
-            children: [for (final dialog in _app.dialogs) dialog],
+          return Form(
+            key: _formState,
+            child: AlertDialog(
+              title: _titleFromDialogInfo(dialog),
+              content: _contentFromDialogInfo(dialog),
+              actions: [
+                for (final label in dialog.actions)
+                  if (label == dialog.defaultAction)
+                    ElevatedButton(
+                      autofocus: dialog is AlertDialogInfo,
+                      child: Text(label.toUpperCase()),
+                      onPressed: () {
+                        if (validate()) {
+                          _formState.currentState?.save();
+                          Navigator.pop(context, label);
+                        }
+                      },
+                    )
+                  else
+                    OutlinedButton(
+                      child: Text(label.toUpperCase()),
+                      onPressed: () => Navigator.pop(context, label),
+                    )
+              ],
+              insetPadding: EdgeInsets.symmetric(horizontal: 240),
+              titlePadding: EdgeInsets.fromLTRB(40, 40, 40, 24),
+              contentPadding: EdgeInsets.fromLTRB(40, 0, 40, 24),
+              actionsPadding: EdgeInsets.only(right: 40, bottom: 24),
+              titleTextStyle: Theme.of(context).textTheme.headline5,
+            ),
           );
-        },
-      ));
+        });
+    if (result != null) {
+      dialog.onAction?.call(result);
+    }
+    dialog.onClose?.call();
+
+    _showAllDialogs(context, index + 1);
+  }
+
+  Widget? _titleFromDialogInfo(DialogInfo info) {
+    switch (info.runtimeType) {
+      case AlertDialogInfo:
+        return Text(info.title!);
+      default:
+        return null;
+    }
+  }
+
+  Widget? _contentFromDialogInfo(DialogInfo info) {
+    switch (info.runtimeType) {
+      case AlertDialogInfo:
+        final dialog = info as AlertDialogInfo;
+        return (dialog.body != null) ? Text(dialog.body!) : null;
+      case PasswordDialogInfo:
+        final dialog = info as PasswordDialogInfo;
+        return PasswordPrompt(dialog);
+      default:
+        return null;
+    }
+  }
 }
diff --git a/session_shells/ermine/shell/lib/src/widgets/dialogs/password_prompt.dart b/session_shells/ermine/shell/lib/src/widgets/dialogs/password_prompt.dart
new file mode 100644
index 0000000..b59a3f2
--- /dev/null
+++ b/session_shells/ermine/shell/lib/src/widgets/dialogs/password_prompt.dart
@@ -0,0 +1,71 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:ermine_utils/ermine_utils.dart';
+import 'package:ermine/src/widgets/dialogs/dialog.dart';
+import 'package:internationalization/strings.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_mobx/flutter_mobx.dart';
+import 'package:mobx/mobx.dart';
+
+/// Defines a widget to collect password in a TextFormField and used as the
+/// content of an AlertDialog.
+class PasswordPrompt extends StatelessWidget {
+  final PasswordDialogInfo info;
+  final _passwordController = TextEditingController();
+  final _showPassword = false.asObservable();
+
+  PasswordPrompt(this.info);
+
+  @override
+  Widget build(BuildContext context) {
+    return Observer(builder: (context) {
+      return Column(
+        mainAxisSize: MainAxisSize.min,
+        mainAxisAlignment: MainAxisAlignment.center,
+        crossAxisAlignment: CrossAxisAlignment.start,
+        children: [
+          // Password prompt.
+          SizedBox(height: 52, width: 440),
+          Text(info.prompt),
+          SizedBox(height: 40),
+
+          // Password text field.
+          TextFormField(
+            key: ValueKey('password'),
+            autofocus: true,
+            autovalidateMode: AutovalidateMode.onUserInteraction,
+            controller: _passwordController,
+            obscureText: !_showPassword.value,
+            decoration: InputDecoration(
+              border: OutlineInputBorder(),
+              labelText: Strings.passwordHint,
+            ),
+            validator: info.validator,
+            onFieldSubmitted: (text) {
+              if (info.validator?.call(text) == null) {
+                Form.of(context)?.save();
+                Navigator.pop(context, info.defaultAction ?? info.actions.last);
+              }
+            },
+            onSaved: info.onSubmit,
+          ),
+          Container(
+            child: Row(
+              crossAxisAlignment: CrossAxisAlignment.center,
+              children: [
+                Checkbox(
+                  onChanged: (value) =>
+                      runInAction(() => _showPassword.value = value == true),
+                  value: _showPassword.value,
+                ),
+                Text(Strings.showPassword)
+              ],
+            ),
+          ),
+        ],
+      );
+    });
+  }
+}
diff --git a/session_shells/ermine/shell/lib/src/widgets/dialogs/text_only_dialog.dart b/session_shells/ermine/shell/lib/src/widgets/dialogs/text_only_dialog.dart
deleted file mode 100644
index d9ea907..0000000
--- a/session_shells/ermine/shell/lib/src/widgets/dialogs/text_only_dialog.dart
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright 2021 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'package:ermine/src/widgets/dialogs/dialog.dart' as ermine;
-import 'package:flutter/material.dart';
-
-/// A dialog consists of only text and action buttons.
-class TextOnlyDialog extends ermine.Dialog {
-  final String? title;
-  final String? body;
-  final Map<String, VoidCallback> buttons;
-
-  const TextOnlyDialog({required this.buttons, this.title, this.body, Key? key})
-      : assert(title != null || body != null),
-        super(key: key);
-
-  @override
-  Widget build(BuildContext context) => AlertDialog(
-        title: (title != null) ? Text(title!) : null,
-        content: (body != null) ? Text(body!) : null,
-        actions: [
-          for (final label in buttons.keys)
-            TextButton(
-              onPressed: buttons[label],
-              child: Text(label.toUpperCase()),
-            ),
-        ],
-        insetPadding: EdgeInsets.symmetric(horizontal: 240),
-        titlePadding: EdgeInsets.fromLTRB(40, 40, 40, 24),
-        contentPadding: EdgeInsets.fromLTRB(40, 0, 40, 24),
-        actionsPadding: EdgeInsets.only(right: 40, bottom: 24),
-        titleTextStyle: Theme.of(context).textTheme.headline5,
-      );
-}
diff --git a/session_shells/ermine/shell/lib/src/widgets/dialogs/textfield_dialog.dart b/session_shells/ermine/shell/lib/src/widgets/dialogs/textfield_dialog.dart
deleted file mode 100644
index 8377f9c..0000000
--- a/session_shells/ermine/shell/lib/src/widgets/dialogs/textfield_dialog.dart
+++ /dev/null
@@ -1,102 +0,0 @@
-// Copyright 2021 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'package:ermine/src/widgets/dialogs/dialog.dart' as ermine;
-import 'package:flutter/material.dart';
-import 'package:internationalization/strings.dart';
-
-/// A dialog that has a [TextField], action buttons, and an optional text
-/// description.
-class TextfieldDialog extends ermine.Dialog {
-  final TextEditingController textController;
-  final bool isPassword;
-  final String? description;
-  final String? fieldLabel;
-  final String? fieldHint;
-  final Map<String, VoidCallback> buttons;
-  final String? errorMessage;
-  final bool autoFocus;
-
-  final _isObscure = ValueNotifier(false);
-
-  TextfieldDialog(
-      {required this.buttons,
-      required this.textController,
-      this.isPassword = false,
-      this.description,
-      this.fieldLabel,
-      this.fieldHint,
-      this.errorMessage,
-      this.autoFocus = true,
-      Key? key})
-      : super(key: key) {
-    if (isPassword) {
-      _isObscure.value = true;
-    }
-  }
-
-  @override
-  Widget build(BuildContext context) => AlertDialog(
-        title: (description != null) ? Text(description!) : null,
-        content: Column(
-          mainAxisSize: MainAxisSize.min,
-          children: [
-            ValueListenableBuilder<bool>(
-              valueListenable: _isObscure,
-              builder: (context, isObscure, _) => Column(
-                mainAxisSize: MainAxisSize.min,
-                children: [
-                  Container(
-                    width: 440,
-                    child: TextField(
-                      controller: textController,
-                      maxLines: 1,
-                      decoration: InputDecoration(
-                        labelText: fieldLabel,
-                        border: OutlineInputBorder(
-                          borderSide:
-                              BorderSide(color: Theme.of(context).dividerColor),
-                          borderRadius: BorderRadius.circular(0),
-                        ),
-                        hintText: fieldHint,
-                        errorText: errorMessage,
-                      ),
-                      autofocus: autoFocus,
-                      obscureText: isObscure,
-                    ),
-                  ),
-                  if (isPassword)
-                    Row(
-                      children: [
-                        Checkbox(
-                          value: !isObscure,
-                          onChanged: (value) =>
-                              _isObscure.value = value != true,
-                        ),
-                        SizedBox(width: 8),
-                        Text(
-                          Strings.showPassword,
-                          style: Theme.of(context).textTheme.bodyText1,
-                        ),
-                      ],
-                    ),
-                ],
-              ),
-            ),
-          ],
-        ),
-        actions: [
-          for (final label in buttons.keys)
-            TextButton(
-              onPressed: buttons[label],
-              child: Text(label.toUpperCase()),
-            ),
-        ],
-        insetPadding: EdgeInsets.symmetric(horizontal: 240),
-        titlePadding: EdgeInsets.fromLTRB(40, 40, 40, 24),
-        contentPadding: EdgeInsets.fromLTRB(40, 0, 40, 0),
-        actionsPadding: EdgeInsets.only(right: 40, bottom: 24),
-        titleTextStyle: Theme.of(context).textTheme.subtitle1,
-      );
-}
diff --git a/session_shells/ermine/shell/lib/src/widgets/overlays.dart b/session_shells/ermine/shell/lib/src/widgets/overlays.dart
index 42953cb..224bb98 100644
--- a/session_shells/ermine/shell/lib/src/widgets/overlays.dart
+++ b/session_shells/ermine/shell/lib/src/widgets/overlays.dart
@@ -5,7 +5,6 @@
 import 'package:ermine/src/states/app_state.dart';
 import 'package:ermine/src/widgets/app_bar.dart';
 import 'package:ermine/src/widgets/app_switcher.dart';
-import 'package:ermine/src/widgets/dialogs/dialogs.dart';
 import 'package:ermine/src/widgets/scrim.dart';
 import 'package:ermine/src/widgets/side_bar.dart';
 import 'package:flutter/widgets.dart';
@@ -46,9 +45,6 @@
 
             // App Switcher.
             if (app.switcherVisible) AppSwitcher(app),
-
-            // Dialogs.
-            if (app.dialogsVisible) Dialogs(app),
           ],
         ),
       );
diff --git a/session_shells/ermine/shell/lib/src/widgets/quick_settings.dart b/session_shells/ermine/shell/lib/src/widgets/quick_settings.dart
index 39c50e5..1a815db 100644
--- a/session_shells/ermine/shell/lib/src/widgets/quick_settings.dart
+++ b/session_shells/ermine/shell/lib/src/widgets/quick_settings.dart
@@ -8,6 +8,7 @@
 import 'package:ermine/src/widgets/settings/shortcut_settings.dart';
 import 'package:ermine/src/widgets/settings/timezone_settings.dart';
 import 'package:ermine/src/widgets/settings/wifi_settings.dart';
+import 'package:ermine_utils/ermine_utils.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_mobx/flutter_mobx.dart';
 import 'package:internationalization/strings.dart';
@@ -49,8 +50,7 @@
                     onChange: state.setTargetChannel,
                     updateAlert: appState.checkingForUpdatesAlert,
                   ),
-                if (state.wifiPageVisible)
-                  WiFiSettings(state: state, onChange: state.setTargetNetwork)
+                if (state.wifiPageVisible) WiFiSettings(state: state)
               ],
             ),
           );
@@ -79,20 +79,40 @@
               mainAxisSize: MainAxisSize.min,
               crossAxisAlignment: CrossAxisAlignment.center,
               children: [
+                // Logout button.
+                OutlinedButton(
+                  child: Icon(Icons.logout),
+                  onPressed: appState.logout,
+                  style: ErmineButtonStyle.outlinedButton(Theme.of(context))
+                      .copyWith(
+                    padding: MaterialStateProperty.all(EdgeInsets.zero),
+                    minimumSize: MaterialStateProperty.all(Size(40, 40)),
+                  ),
+                ).tooltip(Strings.logout),
+                SizedBox(width: 8),
+
                 // Restart button.
-                OutlinedButton.icon(
+                OutlinedButton(
+                  child: Icon(Icons.restart_alt),
                   onPressed: appState.restart,
-                  icon: Icon(Icons.restart_alt),
-                  label: Text(Strings.restart.toUpperCase()),
-                ),
+                  style: ErmineButtonStyle.outlinedButton(Theme.of(context))
+                      .copyWith(
+                    padding: MaterialStateProperty.all(EdgeInsets.zero),
+                    minimumSize: MaterialStateProperty.all(Size(36, 36)),
+                  ),
+                ).tooltip(Strings.restart),
                 SizedBox(width: 8),
 
                 // Power off button.
-                OutlinedButton.icon(
+                OutlinedButton(
+                  child: Icon(Icons.power_settings_new_rounded),
                   onPressed: appState.shutdown,
-                  icon: Icon(Icons.power_settings_new_rounded),
-                  label: Text(Strings.shutdown.toUpperCase()),
-                ),
+                  style: ErmineButtonStyle.outlinedButton(Theme.of(context))
+                      .copyWith(
+                    padding: MaterialStateProperty.all(EdgeInsets.zero),
+                    minimumSize: MaterialStateProperty.all(Size(36, 36)),
+                  ),
+                ).tooltip(Strings.powerOff),
 
                 Spacer(),
                 // Date time.
@@ -115,6 +135,7 @@
               children: [
                 // Switch Theme
                 SwitchListTile(
+                  key: ValueKey('darkMode'),
                   contentPadding: EdgeInsets.symmetric(horizontal: 24),
                   secondary: Icon(Icons.dark_mode),
                   title: Text(Strings.darkMode),
@@ -173,6 +194,8 @@
                     trailing: appState.settingsState.brightnessAuto == true
                         ? Text(Strings.auto.toUpperCase())
                         : OutlinedButton(
+                            style: ErmineButtonStyle.outlinedButton(
+                                Theme.of(context)),
                             onPressed: appState.settingsState.setBrightnessAuto,
                             child: Text(Strings.auto.toUpperCase()),
                           ),
@@ -198,11 +221,15 @@
                     ),
                     trailing: appState.settingsState.volumeMuted == true
                         ? OutlinedButton(
+                            style: ErmineButtonStyle.outlinedButton(
+                                Theme.of(context)),
                             onPressed: () => appState.settingsState
                                 .setVolumeMute(muted: false),
                             child: Text(Strings.unmute.toUpperCase()),
                           )
                         : OutlinedButton(
+                            style: ErmineButtonStyle.outlinedButton(
+                                Theme.of(context)),
                             onPressed: () => appState.settingsState
                                 .setVolumeMute(muted: true),
                             child: Text(Strings.mute.toUpperCase()),
@@ -210,20 +237,29 @@
                   );
                 }),
                 // Wi-Fi
-                ListTile(
-                  contentPadding: EdgeInsets.symmetric(horizontal: 24),
-                  leading: Icon(Icons.wifi),
-                  title: Text(Strings.wifi),
-                  trailing: Wrap(
-                    alignment: WrapAlignment.end,
-                    crossAxisAlignment: WrapCrossAlignment.center,
-                    spacing: 8,
-                    children: [
-                      Icon(Icons.arrow_right),
-                    ],
-                  ),
-                  onTap: appState.settingsState.showWiFiSettings,
-                ),
+                Observer(builder: (_) {
+                  return ListTile(
+                    contentPadding: EdgeInsets.symmetric(horizontal: 24),
+                    leading: Icon(Icons.wifi),
+                    title: Row(
+                      crossAxisAlignment: CrossAxisAlignment.center,
+                      children: [
+                        Text(Strings.wifi),
+                        SizedBox(width: 48),
+                        Expanded(
+                          child: Text(
+                            appState.settingsState.currentNetwork,
+                            overflow: TextOverflow.ellipsis,
+                            textAlign: TextAlign.right,
+                            maxLines: 1,
+                          ),
+                        ),
+                      ],
+                    ),
+                    trailing: Icon(Icons.arrow_right),
+                    onTap: appState.settingsState.showWiFiSettings,
+                  );
+                }),
                 // Channel
                 ListTile(
                   enabled: true,
@@ -254,6 +290,7 @@
                   leading: Icon(Icons.feedback_outlined),
                   title: Text(Strings.feedback),
                   trailing: OutlinedButton(
+                    style: ErmineButtonStyle.outlinedButton(Theme.of(context)),
                     onPressed: appState.launchFeedback,
                     child: Text(Strings.open.toUpperCase()),
                   ),
@@ -265,6 +302,7 @@
                   leading: Icon(Icons.info_outline),
                   title: Text(Strings.openSource),
                   trailing: OutlinedButton(
+                    style: ErmineButtonStyle.outlinedButton(Theme.of(context)),
                     onPressed: appState.launchLicense,
                     child: Text(Strings.open.toUpperCase()),
                   ),
diff --git a/session_shells/ermine/shell/lib/src/widgets/settings/channel_settings.dart b/session_shells/ermine/shell/lib/src/widgets/settings/channel_settings.dart
index dbb67bd..e6b111e 100644
--- a/session_shells/ermine/shell/lib/src/widgets/settings/channel_settings.dart
+++ b/session_shells/ermine/shell/lib/src/widgets/settings/channel_settings.dart
@@ -4,6 +4,7 @@
 
 import 'package:ermine/src/states/settings_state.dart';
 import 'package:ermine/src/widgets/settings/setting_details.dart';
+import 'package:ermine_utils/ermine_utils.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_mobx/flutter_mobx.dart';
 import 'package:internationalization/strings.dart';
@@ -110,6 +111,7 @@
         Padding(
           padding: EdgeInsets.fromLTRB(8, 12, 24, 12),
           child: ElevatedButton(
+            style: ErmineButtonStyle.elevatedButton(Theme.of(context)),
             onPressed: state.targetChannel == '' ? null : updateAlert,
             child: Text(Strings.update.toUpperCase()),
           ),
diff --git a/session_shells/ermine/shell/lib/src/widgets/settings/wifi_settings.dart b/session_shells/ermine/shell/lib/src/widgets/settings/wifi_settings.dart
index 250f89e..10c65da 100644
--- a/session_shells/ermine/shell/lib/src/widgets/settings/wifi_settings.dart
+++ b/session_shells/ermine/shell/lib/src/widgets/settings/wifi_settings.dart
@@ -11,9 +11,8 @@
 /// Defines a widget to control WiFi in [SettingDetails] widget.
 class WiFiSettings extends StatelessWidget {
   final SettingsState state;
-  final ValueChanged<String> onChange;
 
-  const WiFiSettings({required this.state, required this.onChange});
+  const WiFiSettings({required this.state});
 
   @override
   Widget build(BuildContext context) {
@@ -25,19 +24,22 @@
           Expanded(
             child: SettingDetails(
               title: Strings.wifi,
+              // TODO(cwhitten): Uncomment switch when fxb/90428 is fixed.
+              /*trailing: Switch(
+                value: state.clientConnectionsEnabled,
+                onChanged: (value) =>
+                    state.setClientConnectionsEnabled(enabled: value),
+              ),*/
               onBack: state.showAllSettings,
               child: SingleChildScrollView(
                 child: Column(
                   crossAxisAlignment: CrossAxisAlignment.start,
                   children: [
-                    Padding(
-                      padding:
-                          EdgeInsets.symmetric(horizontal: 24, vertical: 16),
-                      child: Text(Strings.savedNetworks),
-                    ),
-                    if (savedNetworks.isEmpty)
-                      ListTile(
-                        title: Text(Strings.loading.toLowerCase()),
+                    if (savedNetworks.isNotEmpty)
+                      Padding(
+                        padding:
+                            EdgeInsets.symmetric(horizontal: 24, vertical: 16),
+                        child: Text(Strings.savedNetworks),
                       ),
                     ListView.builder(
                         shrinkWrap: true,
@@ -45,9 +47,21 @@
                         itemCount: savedNetworks.length,
                         itemBuilder: (context, index) {
                           final networkName = savedNetworks[index].name;
+                          bool currentNetwork =
+                              networkName == state.currentNetwork;
                           final networkIcon = savedNetworks[index].icon;
                           final networkCompatible =
                               savedNetworks[index].compatible;
+                          final networkHasFailedCredentials =
+                              savedNetworks[index].credentialsFailed;
+                          // Add 'connnected' or 'credentials failed' subtitle if applicable
+                          String? networkSubtitle;
+                          if (currentNetwork) {
+                            networkSubtitle = Strings.connected;
+                          }
+                          if (networkHasFailedCredentials) {
+                            networkSubtitle = Strings.incorrectPassword;
+                          }
                           return ListTile(
                             title: Text(networkName,
                                 maxLines: 1,
@@ -61,7 +75,8 @@
                                 color: networkCompatible
                                     ? null
                                     : Theme.of(context).disabledColor),
-                            onTap: () => onChange(savedNetworks[index].name),
+                            onTap: () =>
+                                state.setTargetNetwork(savedNetworks[index]),
                             trailing: PopupMenuButton(
                               itemBuilder: (context) {
                                 return [
@@ -74,6 +89,9 @@
                               onSelected: state.removeNetwork,
                               tooltip: Strings.forget,
                             ),
+                            subtitle: networkSubtitle != null
+                                ? Text(networkSubtitle)
+                                : null,
                           );
                         }),
                     Padding(
@@ -107,10 +125,10 @@
                                 color: networkCompatible
                                     ? null
                                     : Theme.of(context).disabledColor),
-                            onTap: () =>
-                                onChange(availableNetworks[index].name),
-                            trailing: ((state.targetNetwork != '') &&
-                                    (state.targetNetwork == networkName))
+                            onTap: () => state
+                                .setTargetNetwork(availableNetworks[index]),
+                            trailing: ((state.targetNetwork.name != '') &&
+                                    (state.targetNetwork.name == networkName))
                                 ? Icon(Icons.check_outlined)
                                 : null,
                           );
@@ -120,46 +138,82 @@
               ),
             ),
           ),
-          _buildPasswordPrompt(context),
+          _buildNetworkSelection(context),
         ],
       );
     });
   }
 
-  Widget _buildPasswordPrompt(BuildContext context) {
-    TextEditingController textController = TextEditingController();
-    bool networkSelected = state.targetNetwork != '';
+  Widget _buildNetworkSelection(BuildContext context) {
+    if (state.targetNetwork.name == '') {
+      return _buildSelectNetworkPrompt(context);
+    } else {
+      if (state.targetNetwork.isOpen) {
+        return _buildOpenNetworkPrompt(context);
+      } else {
+        return _buildPasswordEntryPrompt(context);
+      }
+    }
+  }
+
+  Widget _buildSelectNetworkPrompt(BuildContext context) {
     return AppBar(
       elevation: 0,
-      title: networkSelected
-          ? TextField(
-              controller: textController,
-              maxLines: 1,
-              decoration: InputDecoration(
-                border: InputBorder.none,
-                hintText: Strings.enterPasswordForNetwork(state.targetNetwork),
-              ),
-            )
-          : Text(
-              Strings.selectNetwork,
-              style: Theme.of(context).textTheme.bodyText2,
-            ),
+      title: Text(
+        Strings.selectNetwork,
+        style: Theme.of(context).textTheme.bodyText2,
+      ),
+      shape: Border(top: BorderSide(color: Theme.of(context).indicatorColor)),
+    );
+  }
+
+  Widget _buildOpenNetworkPrompt(BuildContext context) {
+    return AppBar(
+      elevation: 0,
+      title: Text(
+        Strings.connectToNetwork(state.targetNetwork.name),
+        style: Theme.of(context).textTheme.bodyText2,
+      ),
       shape: Border(top: BorderSide(color: Theme.of(context).indicatorColor)),
       actions: [
-        if (networkSelected)
-          Padding(
-            padding: EdgeInsets.fromLTRB(8, 12, 24, 12),
-            child: ElevatedButton(
-              onPressed: () => _enterPassword(textController),
-              child: Text(Strings.connect),
-            ),
+        Padding(
+          padding: EdgeInsets.fromLTRB(8, 12, 24, 12),
+          child: ElevatedButton(
+            onPressed: state.connectToNetwork,
+            child: Text(Strings.connect),
           ),
+        ),
+      ],
+    );
+  }
+
+  Widget _buildPasswordEntryPrompt(BuildContext context) {
+    return AppBar(
+      elevation: 0,
+      title: TextField(
+        controller: state.networkPasswordTextController,
+        maxLines: 1,
+        decoration: InputDecoration(
+          border: InputBorder.none,
+          hintText: Strings.enterPasswordForNetwork(state.targetNetwork.name),
+        ),
+      ),
+      shape: Border(top: BorderSide(color: Theme.of(context).indicatorColor)),
+      actions: [
+        Padding(
+          padding: EdgeInsets.fromLTRB(8, 12, 24, 12),
+          child: ElevatedButton(
+            onPressed: () =>
+                _enterPassword(state.networkPasswordTextController),
+            child: Text(Strings.connect),
+          ),
+        ),
       ],
     );
   }
 
   void _enterPassword(TextEditingController textController) {
-    state.connectToWPA2Network(textController.text);
+    state.connectToNetwork(textController.text);
     textController.clear();
   }
 }
diff --git a/session_shells/ermine/shell/meta/ermine.cml b/session_shells/ermine/shell/meta/ermine.cml
new file mode 100644
index 0000000..164b053
--- /dev/null
+++ b/session_shells/ermine/shell/meta/ermine.cml
@@ -0,0 +1,191 @@
+{
+    include: [
+        "//sdk/lib/inspect/client.shard.cml",
+
+        // Enable system logging.
+        "syslog/client.shard.cml",
+    ],
+    program: {
+        data: "data/ermine",
+    },
+    children: [
+        {
+            name: "memfs",
+            url: "fuchsia-pkg://fuchsia.com/ermine#meta/memfs.cm",
+        },
+        {
+            name: "element_manager",
+            url: "fuchsia-pkg://fuchsia.com/element_manager#meta/element_manager.cm",
+        },
+        {
+            name: "chrome",
+            url: "fuchsia-pkg://fuchsia.com/chrome#meta/chrome.cm",
+        },
+    ],
+    capabilities: [
+        {
+            protocol: [
+                "fuchsia.element.GraphicalPresenter",
+                "fuchsia.ui.app.ViewProvider",
+            ],
+        },
+        {
+            storage: "tmp",
+            from: "#memfs",
+            subdir: "tmp",
+            backing_dir: "memfs",
+            storage_id: "static_instance_id_or_moniker",
+        },
+    ],
+    use: [
+        {
+            protocol: [
+                "fuchsia.accessibility.semantics.SemanticsManager",
+                "fuchsia.buildinfo.Provider",
+                "fuchsia.cobalt.LoggerFactory",
+                "fuchsia.feedback.CrashReporter",
+                "fuchsia.fonts.Provider",
+                "fuchsia.hardware.power.statecontrol.Admin",
+                "fuchsia.intl.PropertyProvider",
+                "fuchsia.media",
+                "fuchsia.media.Audio",
+                "fuchsia.media.AudioCore",
+                "fuchsia.memory.Monitor",
+                "fuchsia.net.interfaces.State",
+                "fuchsia.power.battery.BatteryManager",
+                "fuchsia.settings.Intl",
+                "fuchsia.settings.Privacy",
+                "fuchsia.ssh.AuthorizedKeys",
+                "fuchsia.ui.activity.Provider",
+                "fuchsia.ui.activity.Tracker",
+                "fuchsia.ui.brightness.Control",
+                "fuchsia.ui.composition.Allocator",
+                "fuchsia.ui.composition.Flatland",
+                "fuchsia.ui.focus.FocusChainListenerRegistry",
+                "fuchsia.ui.input.ImeService",
+                "fuchsia.ui.input.InputDeviceRegistry",
+                "fuchsia.ui.input.PointerCaptureListenerRegistry",
+                "fuchsia.ui.input3.Keyboard",
+                "fuchsia.ui.scenic.Scenic",
+                "fuchsia.ui.shortcut.Registry",
+                "fuchsia.update.channelcontrol.ChannelControl",
+                "fuchsia.update.Manager",
+                "fuchsia.wlan.common",
+                "fuchsia.wlan.policy",
+                "fuchsia.wlan.policy.ClientProvider",
+            ],
+        },
+        {
+            directory: "config-data",
+            from: "parent",
+            rights: [ "r*" ],
+            path: "/config/data",
+        },
+        {
+            storage: "account",
+            path: "/data",
+        },
+        {
+            protocol: "fuchsia.element.Manager",
+            from: "#element_manager",
+            path: "/svc/fuchsia.element.Manager",
+        },
+        {
+            protocol: "fuchsia.element.Manager",
+            from: "#chrome",
+            path: "/svc/fuchsia.element.Manager-chrome",
+        },
+    ],
+    offer: [
+        {
+            protocol: [ "fuchsia.element.GraphicalPresenter" ],
+            from: "self",
+            to: "#element_manager",
+            dependency: "weak",
+        },
+        {
+            protocol: [
+                "fuchsia.logger.LogSink",
+                "fuchsia.media.Audio",
+                "fuchsia.sys.Launcher",
+                "fuchsia.sysmem.Allocator",
+                "fuchsia.tracing.provider.Registry",
+                "fuchsia.ui.composition.Allocator",
+                "fuchsia.ui.composition.Flatland",
+                "fuchsia.ui.input3.Keyboard",
+                "fuchsia.ui.scenic.Scenic",
+            ],
+            from: "parent",
+            to: "#element_manager",
+        },
+
+        // Support Chrome as a static component.
+        {
+            protocol: [
+                "fuchsia.buildinfo.Provider",
+                "fuchsia.fonts.Provider",
+                "fuchsia.intl.PropertyProvider",
+                "fuchsia.logger.LogSink",
+                "fuchsia.media.Audio",
+                "fuchsia.media.AudioDeviceEnumerator",
+                "fuchsia.media.ProfileProvider",
+                "fuchsia.mediacodec.CodecFactory",
+                "fuchsia.memorypressure.Provider",
+                "fuchsia.net.interfaces.State",
+                "fuchsia.net.name.Lookup",
+                "fuchsia.posix.socket.Provider",
+                "fuchsia.process.Launcher",
+                "fuchsia.sysmem.Allocator",
+                "fuchsia.tracing.provider.Registry",
+                "fuchsia.ui.composition.Allocator",
+                "fuchsia.ui.composition.Flatland",
+                "fuchsia.ui.input3.Keyboard",
+                "fuchsia.ui.scenic.Scenic",
+                "fuchsia.vulkan.loader.Loader",
+            ],
+            from: "parent",
+            to: "#chrome",
+        },
+        {
+            protocol: [ "fuchsia.element.GraphicalPresenter" ],
+            from: "self",
+            to: "#chrome",
+            dependency: "weak",
+        },
+        {
+            directory: "root-ssl-certificates",
+            from: "parent",
+            to: [ "#chrome" ],
+        },
+        {
+            storage: [ "account_cache" ],
+            from: "parent",
+            as: "cache",
+            to: "#chrome",
+        },
+        {
+            storage: [ "tmp" ],
+            from: "self",
+            to: "#chrome",
+        },
+        {
+            storage: "account",
+            from: "parent",
+            as: "data",
+            to: "#chrome",
+        },
+    ],
+    expose: [
+        {
+            protocol: [
+                "fuchsia.element.GraphicalPresenter",
+                "fuchsia.ui.app.ViewProvider",
+            ],
+            from: "self",
+        },
+        {
+            protocol: "fuchsia.element.Manager",
+            from: "#element_manager",
+        },
+    ],
+}
diff --git a/session_shells/ermine/shell/meta/ermine.cmx b/session_shells/ermine/shell/meta/ermine.cmx
deleted file mode 100644
index b5d8b1d..0000000
--- a/session_shells/ermine/shell/meta/ermine.cmx
+++ /dev/null
@@ -1,50 +0,0 @@
-{
-    "program": {
-        "data": "data/ermine"
-    },
-    "sandbox": {
-        "features": [
-            "config-data",
-            "isolated-persistent-storage"
-        ],
-        "pkgfs": [
-            "packages"
-        ],
-        "services": [
-            "fuchsia.accessibility.semantics.SemanticsManager",
-            "fuchsia.buildinfo.Provider",
-            "fuchsia.cobalt.LoggerFactory",
-            "fuchsia.device.manager.Administrator",
-            "fuchsia.element.Manager",
-            "fuchsia.feedback.CrashReporter",
-            "fuchsia.fonts.Provider",
-            "fuchsia.intl.PropertyProvider",
-            "fuchsia.logger.LogSink",
-            "fuchsia.media",
-            "fuchsia.media.Audio",
-            "fuchsia.media.AudioCore",
-            "fuchsia.memory.Monitor",
-            "fuchsia.net.interfaces.State",
-            "fuchsia.power.BatteryManager",
-            "fuchsia.settings.Intl",
-            "fuchsia.settings.Privacy",
-            "fuchsia.ssh.AuthorizedKeys",
-            "fuchsia.sys.Environment",
-            "fuchsia.ui.activity.Provider",
-            "fuchsia.ui.activity.Tracker",
-            "fuchsia.ui.brightness.Control",
-            "fuchsia.ui.focus.FocusChainListenerRegistry",
-            "fuchsia.ui.input.ImeService",
-            "fuchsia.ui.input.InputDeviceRegistry",
-            "fuchsia.ui.input.PointerCaptureListenerRegistry",
-            "fuchsia.ui.input3.Keyboard",
-            "fuchsia.ui.scenic.Scenic",
-            "fuchsia.ui.shortcut.Registry",
-            "fuchsia.update.Manager",
-            "fuchsia.update.channelcontrol.ChannelControl",
-            "fuchsia.wlan.common",
-            "fuchsia.wlan.policy",
-            "fuchsia.wlan.policy.ClientProvider"
-        ]
-    }
-}
diff --git a/session_shells/ermine/shell/test/app_widget_test.dart b/session_shells/ermine/shell/test/app_widget_test.dart
index 19932c1..4ffdb2b 100644
--- a/session_shells/ermine/shell/test/app_widget_test.dart
+++ b/session_shells/ermine/shell/test/app_widget_test.dart
@@ -11,6 +11,7 @@
 import 'package:ermine/src/widgets/app.dart';
 import 'package:ermine/src/widgets/app_view.dart';
 import 'package:ermine/src/widgets/overlays.dart';
+import 'package:ermine/src/widgets/dialogs/dialogs.dart';
 import 'package:ermine_utils/ermine_utils.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_test/flutter_test.dart';
@@ -40,8 +41,9 @@
     final controller = StreamController<Locale>();
     final stream = controller.stream.asObservable();
     when(state.locale).thenAnswer((_) => stream.value);
-    when(state.views).thenAnswer((_) => <ViewState>[].asObservable());
+    when(state.views).thenAnswer((_) => <ViewState>[]);
     when(state.overlaysVisible).thenAnswer((_) => false);
+    when(state.dialogsVisible).thenAnswer((_) => false);
 
     await tester.pumpWidget(app);
     // app should be OffStage until locale is pushed.
@@ -70,9 +72,10 @@
     when(state.locale).thenAnswer((_) => stream.value);
 
     // Create one view.
-    when(state.views).thenAnswer((_) => [MockViewState()].asObservable());
+    when(state.views).thenAnswer((_) => [MockViewState()]);
     // Show overlays.
     when(state.overlaysVisible).thenAnswer((_) => true);
+    when(state.dialogsVisible).thenAnswer((_) => false);
 
     await tester.pumpWidget(app);
     await tester.pumpAndSettle();
@@ -80,6 +83,29 @@
     expect(find.byKey(ValueKey(Overlays)), findsOneWidget);
     await controller.close();
   });
+
+  testWidgets('Dialogs are visible', (tester) async {
+    final controller = StreamController<Locale>();
+    final stream =
+        controller.stream.asObservable(initialValue: Locale('en', 'US'));
+    when(state.locale).thenAnswer((_) => stream.value);
+    when(state.views).thenAnswer((_) => []);
+
+    // Show dialogs.
+    final dialogsVisible = true.asObservable();
+    when(state.overlaysVisible).thenAnswer((_) => false);
+    when(state.dialogsVisible).thenAnswer((_) => dialogsVisible.value);
+
+    await tester.pumpWidget(app);
+    await tester.pumpAndSettle();
+    expect(find.byKey(ValueKey(Dialogs)), findsOneWidget);
+
+    runInAction(() => dialogsVisible.value = false);
+    await tester.pumpAndSettle();
+    expect(find.byKey(ValueKey(Dialogs)), findsNothing);
+
+    await controller.close();
+  });
 }
 
 class MockAppState extends Mock implements AppState {}
diff --git a/session_shells/ermine/utils/BUILD.gn b/session_shells/ermine/utils/BUILD.gn
index 83f7295..47188c6 100644
--- a/session_shells/ermine/utils/BUILD.gn
+++ b/session_shells/ermine/utils/BUILD.gn
@@ -11,11 +11,11 @@
   sources = [
     "ermine_utils.dart",
     "src/crash.dart",
-    "src/fuchsia_keyboard.dart",
     "src/mobx_disposable.dart",
     "src/mobx_extensions.dart",
     "src/themes.dart",
     "src/view_handle.dart",
+    "src/widget_extension.dart",
     "src/widget_factory.dart",
   ]
   deps = [
diff --git a/session_shells/ermine/utils/lib/ermine_utils.dart b/session_shells/ermine/utils/lib/ermine_utils.dart
index 572eef5..3a8c5a9 100644
--- a/session_shells/ermine/utils/lib/ermine_utils.dart
+++ b/session_shells/ermine/utils/lib/ermine_utils.dart
@@ -3,9 +3,9 @@
 // found in the LICENSE file.
 
 export 'src/crash.dart';
-export 'src/fuchsia_keyboard.dart';
 export 'src/mobx_disposable.dart';
 export 'src/mobx_extensions.dart';
 export 'src/themes.dart';
 export 'src/view_handle.dart';
+export 'src/widget_extension.dart';
 export 'src/widget_factory.dart';
diff --git a/session_shells/ermine/utils/lib/src/crash.dart b/session_shells/ermine/utils/lib/src/crash.dart
index 8bfdd37..83db8e5 100644
--- a/session_shells/ermine/utils/lib/src/crash.dart
+++ b/session_shells/ermine/utils/lib/src/crash.dart
@@ -68,7 +68,7 @@
 
     // Generate [CrashReport] from errorType, errorMessage and stackTrace.
     final report = CrashReport(
-      programName: 'ermine.cmx',
+      programName: 'ermine.cm',
       programUptime: uptime.inMilliseconds * 1000,
       specificReport: SpecificCrashReport.withDart(
         RuntimeCrashReport(
diff --git a/session_shells/ermine/utils/lib/src/fuchsia_keyboard.dart b/session_shells/ermine/utils/lib/src/fuchsia_keyboard.dart
deleted file mode 100644
index 1c2ee80..0000000
--- a/session_shells/ermine/utils/lib/src/fuchsia_keyboard.dart
+++ /dev/null
@@ -1,104 +0,0 @@
-// Copyright 2021 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'package:flutter/services.dart';
-import 'package:flutter/widgets.dart';
-
-/// Defines the mapping of Fuchsia keys to application [Intent]s.
-///
-/// This is needed because currently the key mapping for Fuchsia in Flutter
-/// Framework is broken.
-class FuchsiaKeyboard {
-  // Fuchsia keyboard HID usage values are defined in (page 53):
-  // https://www.usb.org/sites/default/files/documents/hut1_12v2.pdf
-
-  static const int kHidUsagePageMask = 0x70000;
-  static const int kFuchsiaKeyIdPlane = LogicalKeyboardKey.fuchsiaPlane;
-
-  static const kEnter = LogicalKeyboardKey(40 | kFuchsiaKeyIdPlane);
-  static const kBackspace = LogicalKeyboardKey(42 | kFuchsiaKeyIdPlane);
-  static const kDelete = LogicalKeyboardKey(76 | kFuchsiaKeyIdPlane);
-  static const kEscape = LogicalKeyboardKey(41 | kFuchsiaKeyIdPlane);
-  static const kTab = LogicalKeyboardKey(43 | kFuchsiaKeyIdPlane);
-  static const kArrowLeft = LogicalKeyboardKey(80 | kFuchsiaKeyIdPlane);
-  static const kArrowRight = LogicalKeyboardKey(79 | kFuchsiaKeyIdPlane);
-  static const kArrowDown = LogicalKeyboardKey(81 | kFuchsiaKeyIdPlane);
-  static const kArrowUp = LogicalKeyboardKey(82 | kFuchsiaKeyIdPlane);
-  static const kPageUp = LogicalKeyboardKey(75 | kFuchsiaKeyIdPlane);
-  static const kPageDown = LogicalKeyboardKey(78 | kFuchsiaKeyIdPlane);
-
-  static const Map<ShortcutActivator, Intent> defaultShortcuts =
-      <ShortcutActivator, Intent>{
-    // Activation
-    SingleActivator(kEnter): ActivateIntent(),
-    SingleActivator(LogicalKeyboardKey.space): ActivateIntent(),
-
-    // Dismissal
-    SingleActivator(kEscape): DismissIntent(),
-
-    // Keyboard traversal.
-    SingleActivator(kTab): NextFocusIntent(),
-    SingleActivator(kTab, shift: true): PreviousFocusIntent(),
-    SingleActivator(kArrowLeft):
-        DirectionalFocusIntent(TraversalDirection.left),
-    SingleActivator(kArrowRight):
-        DirectionalFocusIntent(TraversalDirection.right),
-    SingleActivator(kArrowDown):
-        DirectionalFocusIntent(TraversalDirection.down),
-    SingleActivator(kArrowUp): DirectionalFocusIntent(TraversalDirection.up),
-
-    // Scrolling
-    SingleActivator(kArrowUp, control: true):
-        ScrollIntent(direction: AxisDirection.up),
-    SingleActivator(kArrowDown, control: true):
-        ScrollIntent(direction: AxisDirection.down),
-    SingleActivator(kArrowLeft, control: true):
-        ScrollIntent(direction: AxisDirection.left),
-    SingleActivator(kArrowRight, control: true):
-        ScrollIntent(direction: AxisDirection.right),
-    SingleActivator(kPageUp): ScrollIntent(
-        direction: AxisDirection.up, type: ScrollIncrementType.page),
-    SingleActivator(kPageDown): ScrollIntent(
-        direction: AxisDirection.down, type: ScrollIncrementType.page),
-  };
-
-  static const Map<ShortcutActivator, Intent> defaultEditingShortcuts =
-      <ShortcutActivator, Intent>{
-    SingleActivator(kBackspace): DeleteTextIntent(),
-    SingleActivator(kBackspace, control: true): DeleteByWordTextIntent(),
-    SingleActivator(kBackspace, alt: true): DeleteByLineTextIntent(),
-    SingleActivator(kDelete): DeleteForwardTextIntent(),
-    SingleActivator(kDelete, control: true): DeleteForwardByWordTextIntent(),
-    SingleActivator(kDelete, alt: true): DeleteForwardByLineTextIntent(),
-    SingleActivator(kArrowDown, alt: true): MoveSelectionToEndTextIntent(),
-    SingleActivator(kArrowLeft, alt: true): MoveSelectionLeftByLineTextIntent(),
-    SingleActivator(kArrowRight, alt: true):
-        MoveSelectionRightByLineTextIntent(),
-    SingleActivator(kArrowUp, alt: true): MoveSelectionToStartTextIntent(),
-    SingleActivator(kArrowDown, shift: true, alt: true):
-        ExpandSelectionToEndTextIntent(),
-    SingleActivator(kArrowLeft, shift: true, alt: true):
-        ExpandSelectionLeftByLineTextIntent(),
-    SingleActivator(kArrowRight, shift: true, alt: true):
-        ExpandSelectionRightByLineTextIntent(),
-    SingleActivator(kArrowUp, shift: true, alt: true):
-        ExpandSelectionToStartTextIntent(),
-    SingleActivator(kArrowDown): MoveSelectionDownTextIntent(),
-    SingleActivator(kArrowLeft): MoveSelectionLeftTextIntent(),
-    SingleActivator(kArrowRight): MoveSelectionRightTextIntent(),
-    SingleActivator(kArrowUp): MoveSelectionUpTextIntent(),
-    SingleActivator(kArrowLeft, control: true):
-        MoveSelectionLeftByWordTextIntent(),
-    SingleActivator(kArrowRight, control: true):
-        MoveSelectionRightByWordTextIntent(),
-    SingleActivator(kArrowLeft, shift: true, control: true):
-        ExtendSelectionLeftByWordTextIntent(),
-    SingleActivator(kArrowRight, shift: true, control: true):
-        ExtendSelectionRightByWordTextIntent(),
-    SingleActivator(kArrowDown, shift: true): ExtendSelectionDownTextIntent(),
-    SingleActivator(kArrowLeft, shift: true): ExtendSelectionLeftTextIntent(),
-    SingleActivator(kArrowRight, shift: true): ExtendSelectionRightTextIntent(),
-    SingleActivator(kArrowUp, shift: true): ExtendSelectionUpTextIntent(),
-  };
-}
diff --git a/session_shells/ermine/utils/lib/src/themes.dart b/session_shells/ermine/utils/lib/src/themes.dart
index cbf5eae..13193a9 100644
--- a/session_shells/ermine/utils/lib/src/themes.dart
+++ b/session_shells/ermine/utils/lib/src/themes.dart
@@ -48,6 +48,7 @@
         outlinedButtonTheme: OutlinedButtonThemeData(
           style: OutlinedButton.styleFrom(
             primary: Colors.black,
+            onSurface: Colors.grey,
             side: BorderSide(color: Colors.black),
             padding: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
             shape:
@@ -133,6 +134,7 @@
         outlinedButtonTheme: OutlinedButtonThemeData(
           style: OutlinedButton.styleFrom(
             primary: Colors.white,
+            onSurface: Colors.grey,
             side: BorderSide(color: Colors.white),
             padding: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
             shape:
@@ -178,3 +180,55 @@
         ),
       );
 }
+
+class ErmineButtonStyle {
+  const ErmineButtonStyle._();
+
+  static ButtonStyle elevatedButton(ThemeData theme) => ButtonStyle(
+        overlayColor: MaterialStateProperty.resolveWith<Color?>(
+            (Set<MaterialState> states) {
+          if (states.contains(MaterialState.hovered)) {
+            return theme.bottomAppBarColor.withOpacity(0.24);
+          }
+          if (states.contains(MaterialState.focused)) {
+            return theme.bottomAppBarColor.withOpacity(0.38);
+          }
+          if (states.contains(MaterialState.pressed)) {
+            return theme.colorScheme.primary.withOpacity(0.58);
+          }
+          return null;
+        }),
+      );
+
+  static ButtonStyle outlinedButton(ThemeData theme) => ButtonStyle(
+        overlayColor: MaterialStateProperty.resolveWith<Color?>(
+            (Set<MaterialState> states) {
+          if (states.contains(MaterialState.hovered)) {
+            return theme.dividerColor.withOpacity(0.12);
+          }
+          if (states.contains(MaterialState.focused)) {
+            return theme.dividerColor.withOpacity(0.24);
+          }
+          if (states.contains(MaterialState.pressed)) {
+            return theme.colorScheme.primary.withOpacity(0.58);
+          }
+          return null;
+        }),
+      );
+
+  static ButtonStyle textButton(ThemeData theme) => ButtonStyle(
+        overlayColor: MaterialStateProperty.resolveWith<Color?>(
+            (Set<MaterialState> states) {
+          if (states.contains(MaterialState.hovered)) {
+            return theme.dividerColor.withOpacity(0.12);
+          }
+          if (states.contains(MaterialState.focused)) {
+            return theme.dividerColor.withOpacity(0.24);
+          }
+          if (states.contains(MaterialState.pressed)) {
+            return theme.colorScheme.primary.withOpacity(0.58);
+          }
+          return null;
+        }),
+      );
+}
diff --git a/session_shells/ermine/utils/lib/src/view_handle.dart b/session_shells/ermine/utils/lib/src/view_handle.dart
index 4658949..6c836fa 100644
--- a/session_shells/ermine/utils/lib/src/view_handle.dart
+++ b/session_shells/ermine/utils/lib/src/view_handle.dart
@@ -2,11 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import 'dart:async';
 
 import 'package:fidl_fuchsia_ui_views/fidl_async.dart';
-import 'package:fuchsia_logger/logger.dart';
-import 'package:fuchsia_scenic_flutter/fuchsia_view.dart' as fuchsia_view;
 
 /// A helper class that wraps [ViewRef] and provides convenient accessors.
 class ViewHandle {
@@ -30,13 +27,4 @@
 
   int get handle => viewRef.reference.handle?.handle ?? -1;
 
-  /// Request focus to be transfered to the view with [handle].
-  Future<void> focus() async {
-    try {
-      await fuchsia_view.FocusState.instance.requestFocus(handle);
-      // ignore: avoid_catches_without_on_clauses
-    } catch (e) {
-      log.warning('Exception on requestFocus: $e ${StackTrace.current}');
-    }
-  }
 }
diff --git a/session_shells/ermine/utils/lib/src/widget_extension.dart b/session_shells/ermine/utils/lib/src/widget_extension.dart
new file mode 100644
index 0000000..7c11453
--- /dev/null
+++ b/session_shells/ermine/utils/lib/src/widget_extension.dart
@@ -0,0 +1,16 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter/material.dart';
+
+/// Helper widget extensions to help flatten widget trees.
+extension WidgetExtension on Widget {
+  Widget tooltip(String message) {
+    return Tooltip(message: message, child: this);
+  }
+
+  Widget padding(EdgeInsetsGeometry value) {
+    return Padding(padding: value, child: this);
+  }
+}
diff --git a/tests/BUILD.gn b/tests/BUILD.gn
index 0d93392..f1424e3 100644
--- a/tests/BUILD.gn
+++ b/tests/BUILD.gn
@@ -11,10 +11,7 @@
 
   source_dir = "lib"
 
-  sources = [
-    "ermine_driver.dart",
-    "simple_browser_driver.dart",
-  ]
+  sources = [ "ermine_driver.dart" ]
 
   deps = [
     "//sdk/fidl/fuchsia.input",
@@ -23,6 +20,7 @@
     "//sdk/testing/sl4f/client",
     "//sdk/testing/sl4f/flutter_driver_sl4f",
     "//third_party/dart-pkg/git/flutter/packages/flutter_driver",
+    "//third_party/dart-pkg/git/flutter/packages/fuchsia_remote_debug_protocol",
     "//third_party/dart-pkg/pub/image",
     "//third_party/dart-pkg/pub/test",
   ]
diff --git a/tests/chrome/BUILD.gn b/tests/chrome/BUILD.gn
index 9a21cfb..793e95a 100644
--- a/tests/chrome/BUILD.gn
+++ b/tests/chrome/BUILD.gn
@@ -5,6 +5,15 @@
 import("//build/dart/test.gni")
 import("//build/testing/environments.gni")
 
+# E2E product test runtime dependencies specific to end to end product tests for
+# products in src/experiences.
+#
+# This is pulled from workstation_pro.gni.
+group("end_to_end_deps") {
+  testonly = true
+  public_deps = [ "//src/experiences/bin/ermine_testserver" ]
+}
+
 dart_test("workstation_chrome_smoke_test") {
   null_safe = true
   sources = [ "workstation_chrome_smoke_test.dart" ]
@@ -14,28 +23,62 @@
     "//sdk/testing/sl4f/client",
     "//sdk/testing/sl4f/flutter_driver_sl4f",
     "//src/experiences/tests:ermine_driver",
+    "//third_party/dart-pkg/git/flutter/packages/flutter_driver",
     "//third_party/dart-pkg/pub/test",
   ]
 
-  environments = [ atlas_env ]
+  environments = [
+    # TODO(fxbug.dev/91950): Reenable on AEMU after Screenshots on Flatland is not flaky.
+    {
+      dimensions = {
+        device_type = "Intel NUC Kit NUC7i5DNHE"
+      }
+      tags = [ "e2e-fyi" ]
+    },
+    atlas_env,
+  ]
 }
 
-copy("runtime_deps") {
-  _data_dir = "$target_gen_dir/runtime_deps"
+dart_test("workstation_chrome_advanced_smoke_test") {
+  null_safe = true
+  sources = [ "workstation_chrome_advanced_smoke_test.dart" ]
 
-  sources = [ "//prebuilt/third_party/chromedriver/linux-x64/chromedriver" ]
+  deps = [
+    "//sdk/fidl/fuchsia.input",
+    "//sdk/testing/sl4f/client",
+    "//sdk/testing/sl4f/flutter_driver_sl4f",
+    "//src/experiences/tests:ermine_driver",
+    "//third_party/dart-pkg/git/flutter/packages/flutter_driver",
+    "//third_party/dart-pkg/pub/test",
+  ]
 
-  outputs = [ "$_data_dir/{{source_file_part}}" ]
+  environments = [
+    # TODO(fxbug.dev/91950): Reenable on AEMU after Screenshots on Flatland is not flaky.
+    {
+      dimensions = {
+        device_type = "Intel NUC Kit NUC7i5DNHE"
+      }
+      tags = [ "e2e-fyi" ]
+    },
 
-  metadata = {
-    test_runtime_deps = [ "$_data_dir/chromedriver" ]
-  }
+    # TODO(fxbug.dev/94042): Reenable on Atlas after non-hermetic interactions
+    # with experiences_ermine_smoke_e2e_test are resolved.
+    {
+      dimensions = {
+        device_type = "Atlas"
+      }
+      tags = [ "e2e-fyi" ]
+    },
+  ]
 }
 
 group("test") {
   testonly = true
   if (is_host && is_linux) {
     # Chromedriver prebuilt is only available for linux-x64
-    deps = [ ":workstation_chrome_smoke_test($host_toolchain)" ]
+    deps = [
+      ":workstation_chrome_advanced_smoke_test($host_toolchain)",
+      ":workstation_chrome_smoke_test($host_toolchain)",
+    ]
   }
 }
diff --git a/tests/chrome/test/workstation_chrome_advanced_smoke_test.dart b/tests/chrome/test/workstation_chrome_advanced_smoke_test.dart
new file mode 100644
index 0000000..c4e8a52
--- /dev/null
+++ b/tests/chrome/test/workstation_chrome_advanced_smoke_test.dart
@@ -0,0 +1,112 @@
+// Copyright 2021 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// ignore_for_file: import_of_legacy_library_into_null_safe
+import 'dart:math';
+
+import 'package:ermine_driver/ermine_driver.dart';
+import 'package:fidl_fuchsia_input/fidl_async.dart';
+import 'package:flutter_driver/flutter_driver.dart';
+import 'package:sl4f/sl4f.dart';
+import 'package:test/test.dart';
+
+const chromiumUrl = 'fuchsia-pkg://fuchsia.com/chrome#meta/chrome.cm';
+const testserverUrl =
+    'fuchsia-pkg://fuchsia.com/ermine_testserver#meta/ermine_testserver.cmx';
+
+void main() {
+  late Sl4f sl4f;
+  late ErmineDriver ermine;
+  late Input input;
+
+  setUpAll(() async {
+    sl4f = Sl4f.fromEnvironment();
+    await sl4f.startServer();
+
+    ermine = ErmineDriver(sl4f);
+    await ermine.setUp();
+
+    input = Input(sl4f);
+    print('Set up Input');
+  });
+
+  tearDownAll(() async {
+    await ermine.tearDown();
+    print('Tore down Ermine flutter driver');
+    await sl4f.stopServer();
+    print('Stopped sl4f server');
+    sl4f.close();
+    print('Closed sl4f');
+  });
+
+  test('Chrome browser should be able to access and render web pages.',
+      () async {
+    // Launches test server app
+    expect(await ermine.launch(testserverUrl), isTrue);
+    await ermine.driver.waitUntilNoTransientCallbacks();
+    print('Launched the test server.');
+
+    // TODO(fxb/94441): Launch Chromium using [ErmineDriver.launch] once the blocker is fixed.
+    // Opens the app launcher and find the Chromium app entry
+    print('Opening the app launcher');
+    await ermine.driver.requestData('launcher');
+    await ermine.driver.waitUntilNoTransientCallbacks();
+    final chromiumEntry = find.text('Chromium');
+    await ermine.driver.waitFor(chromiumEntry);
+    print('Found Chromium app entry on the app launcher');
+
+    // Launch Chromium app
+    await ermine.driver.tap(chromiumEntry);
+    print('Tapped Chromium app entry');
+    await ermine.driver.waitUntilNoTransientCallbacks();
+    print('Launched Chromium');
+
+    final snapshot = await ermine.waitForView(chromiumUrl, testForFocus: true);
+    expect(snapshot.url, chromiumUrl);
+    print('A Chromium view is presented');
+
+    const blueUrl = 'http://127.0.0.1:8080/blue.html';
+    await input.text(blueUrl, keyEventDuration: Duration(milliseconds: 50));
+    print('Typed in $blueUrl to the browser');
+    await input.keyPress(kEnterKey);
+    print('Pressed Enter');
+
+    const blue = 0xffff0000; // (0xAABBGGRR)
+    Map<int, int> histogram;
+
+    await Future.delayed(Duration(seconds: 3));
+    final isBlue = await ermine.waitFor(() async {
+      print('Take a screenshot...');
+      final screenshot = await ermine.screenshot(Rectangle(500, 500, 100, 100));
+      histogram = ermine.histogram(screenshot);
+      print('Color key: ${histogram.keys.first}');
+      print('Color value: ${histogram.values.first}');
+      if (histogram.keys.length == 1 && histogram[blue] == 10000) {
+        return true;
+      }
+      return false;
+    }, timeout: Duration(minutes: 2));
+
+    expect(isBlue, isTrue);
+    print('Verified the expected background color');
+
+    // Close Chromium
+    print('Closing the Chromium View');
+    await ermine.threeKeyShortcut(Key.leftCtrl, Key.leftShift, Key.w);
+    await ermine.driver.waitUntilNoTransientCallbacks();
+    await ermine.waitForAction('close');
+    print('Verified that Ermine took CLOSE action');
+    expect(await ermine.waitForViewAbsent(chromiumUrl), true);
+    print('Closed Chromium');
+
+    // Close the test server.
+    print('Closing the test server');
+    await ermine.threeKeyShortcut(Key.leftCtrl, Key.leftShift, Key.w);
+    await ermine.driver.waitUntilNoTransientCallbacks();
+    await ermine.waitForAction('close');
+    print('Verified that Ermine took CLOSE action');
+    expect(await ermine.waitForViewAbsent(testserverUrl), true);
+    print('Closed test server');
+  }, timeout: Timeout(Duration(minutes: 3)));
+}
diff --git a/tests/chrome/test/workstation_chrome_smoke_test.dart b/tests/chrome/test/workstation_chrome_smoke_test.dart
index 49d20fa..b1cfab6 100644
--- a/tests/chrome/test/workstation_chrome_smoke_test.dart
+++ b/tests/chrome/test/workstation_chrome_smoke_test.dart
@@ -3,12 +3,15 @@
 // found in the LICENSE file.
 
 // ignore_for_file: import_of_legacy_library_into_null_safe
+import 'dart:math';
+
 import 'package:ermine_driver/ermine_driver.dart';
 import 'package:fidl_fuchsia_input/fidl_async.dart';
+import 'package:flutter_driver/flutter_driver.dart';
 import 'package:sl4f/sl4f.dart';
 import 'package:test/test.dart';
 
-const chromeUrl = 'fuchsia-pkg://fuchsia.com/chrome#meta/chrome_v1.cmx';
+const chromiumUrl = 'fuchsia-pkg://fuchsia.com/chrome#meta/chrome.cm';
 
 void main() {
   late Sl4f sl4f;
@@ -32,19 +35,46 @@
   });
 
   test('Should be able to launch Chrome browser.', () async {
-    await ermine.launch(chromeUrl);
+    // Launches Chromium app
+    // TODO(fxb/94441): Launch Chromium using [ErmineDriver.launch] once the blocker is fixed.
+    final chromiumEntry = find.text('Chromium');
+    await ermine.driver.waitFor(chromiumEntry);
+    print('Found Chromium app entry');
+    await ermine.driver.tap(chromiumEntry);
+    print('Tapped Chromium app entry');
     await ermine.driver.waitUntilNoTransientCallbacks();
-    print('Launched Chrome');
+    print('Launched Chromium');
 
-    final snapshot = await ermine.waitForView(chromeUrl);
-    expect(snapshot.focused, true);
-    expect(snapshot.url, chromeUrl);
-    print('A Chrome view is presented');
+    final snapshot = await ermine.waitForView(chromiumUrl, testForFocus: true);
+    expect(snapshot.url, chromiumUrl);
+    print('A Chromium view is presented');
+
+    // Takes a screenshot and checks the color
+    const white = 0xffffffff; // (0xAABBGGRR)
+    Map<int, int> histogram;
+
+    await Future.delayed(Duration(seconds: 3));
+    final isWhite = await ermine.waitFor(() async {
+      print('Take a screenshot...');
+      final screenshot = await ermine.screenshot(Rectangle(500, 500, 100, 100));
+      histogram = ermine.histogram(screenshot);
+      print('Color key: ${histogram.keys.first}');
+      print('Color value: ${histogram.values.first}');
+      if (histogram.keys.length == 1 && histogram[white] == 10000) {
+        return true;
+      }
+      return false;
+    }, timeout: Duration(minutes: 2));
+
+    expect(isWhite, isTrue);
+    print('Verified the expected background color');
 
     // Close the Chrome view.
     await ermine.threeKeyShortcut(Key.leftCtrl, Key.leftShift, Key.w);
     await ermine.driver.waitUntilNoTransientCallbacks();
-    expect(await ermine.waitForViewAbsent(chromeUrl), true);
-    print('Closed Chrome');
-  });
+    await ermine.waitForAction('close');
+    print('Verified that Ermine took CLOSE action');
+    expect(await ermine.waitForViewAbsent(chromiumUrl), true);
+    print('Closed Chromium');
+  }, timeout: Timeout(Duration(minutes: 2)));
 }
diff --git a/tests/e2e/BUILD.gn b/tests/e2e/BUILD.gn
index 8145cc4..298732a 100644
--- a/tests/e2e/BUILD.gn
+++ b/tests/e2e/BUILD.gn
@@ -11,18 +11,20 @@
 # This is pulled from workstation.gni.
 group("end_to_end_deps") {
   testonly = true
-  public_deps = [ "//src/experiences/bin/ermine_testserver" ]
+  public_deps = [
+    "//src/experiences/bin/ermine_testserver",
+    "//src/sys/tools/stash_ctl",
+  ]
 }
 
-_service_account =
-    "fuchsia-e2e-auth@fuchsia-cloud-api-for-test.iam.gserviceaccount.com"
-
-host_test_data("scuba_goldens") {
-  sources = [
-    "//src/experiences/tests/e2e/test/scuba_goldens/simple_browser_rearranging_tab_after.png",
-    "//src/experiences/tests/e2e/test/scuba_goldens/simple_browser_rearranging_tab_before.png",
-  ]
-  outputs = [ "$root_out_dir/scuba_goldens/{{source_file_part}}" ]
+if (is_host) {
+  host_test_data("scuba_goldens") {
+    sources = [
+      "//src/experiences/tests/e2e/test/scuba_goldens/simple_browser_rearranging_tab_after.png",
+      "//src/experiences/tests/e2e/test/scuba_goldens/simple_browser_rearranging_tab_before.png",
+    ]
+    outputs = [ "$root_out_dir/scuba_goldens/{{source_file_part}}" ]
+  }
 }
 
 dart_test("experiences_ermine_session_shell_e2e_test") {
@@ -107,52 +109,6 @@
   ]
 }
 
-dart_test("experiences_ermine_simple_browser_e2e_test") {
-  null_safe = true
-  sources = [ "ermine_session_shell_simple_browser_test.dart" ]
-
-  deps = [
-    ":scuba_goldens",
-    "//sdk/fidl/fuchsia.input",
-    "//sdk/fidl/fuchsia.ui.input",
-    "//sdk/fidl/fuchsia.ui.input3",
-    "//sdk/testing/gcloud_lib",
-    "//sdk/testing/sl4f/client",
-    "//sdk/testing/sl4f/flutter_driver_sl4f",
-    "//src/experiences/tests:ermine_driver",
-    "//third_party/dart-pkg/git/flutter/packages/flutter_driver",
-    "//third_party/dart-pkg/pub/image",
-    "//third_party/dart-pkg/pub/test",
-    "//third_party/dart-pkg/pub/webdriver",
-  ]
-
-  non_dart_deps = [ ":runtime_deps" ]
-
-  environments = [
-    {
-      dimensions = {
-        device_type = "AEMU"
-      }
-      service_account = _service_account
-      tags = [ "e2e-fyi" ]
-    },
-    {
-      dimensions = {
-        device_type = "Intel NUC Kit NUC7i5DNHE"
-      }
-      service_account = _service_account
-      tags = [ "e2e-fyi" ]
-    },
-    {
-      dimensions = {
-        device_type = "Atlas"
-      }
-      service_account = _service_account
-      tags = [ "e2e-fyi" ]
-    },
-  ]
-}
-
 dart_test("experiences_ermine_smoke_e2e_test") {
   null_safe = true
   sources = [ "ermine_smoke_test.dart" ]
@@ -170,11 +126,7 @@
   ]
 
   environments = [
-    {
-      dimensions = {
-        device_type = "AEMU"
-      }
-    },
+    # TODO(fxbug.dev/91950): Reenable on AEMU after Screenshots on Flatland is not flaky.
     {
       dimensions = {
         device_type = "Intel NUC Kit NUC7i5DNHE"
@@ -184,6 +136,9 @@
       dimensions = {
         device_type = "Atlas"
       }
+
+      # TODO(fxbug.dev/650923): De-flake and re-enable this test.
+      tags = [ "e2e-fyi" ]
     },
   ]
 }
@@ -206,7 +161,6 @@
     # Chromedriver prebuilt is only available for linux-x64
     deps = [
       ":experiences_ermine_session_shell_e2e_test($host_toolchain)",
-      ":experiences_ermine_simple_browser_e2e_test($host_toolchain)",
       ":experiences_ermine_smoke_e2e_test($host_toolchain)",
       ":experiences_ermine_terminal_e2e_test($host_toolchain)",
     ]
diff --git a/tests/e2e/test/ermine_session_shell_ask_test.dart b/tests/e2e/test/ermine_session_shell_ask_test.dart
index e27cedc..9da7eb6 100644
--- a/tests/e2e/test/ermine_session_shell_ask_test.dart
+++ b/tests/e2e/test/ermine_session_shell_ask_test.dart
@@ -38,8 +38,7 @@
 
     // The inspect data should show that the view has focus.
     const componentUrl = 'fuchsia-pkg://fuchsia.com/terminal#meta/terminal.cmx';
-    final inspect = await ermine.waitForView(componentUrl);
-    expect(inspect.focused, isTrue);
+    await ermine.waitForView(componentUrl, testForFocus: true);
 
     // Close the terminal view.
     await ermine.driver.requestData('close');
diff --git a/tests/e2e/test/ermine_session_shell_simple_browser_test.dart b/tests/e2e/test/ermine_session_shell_simple_browser_test.dart
deleted file mode 100644
index f60b6d1..0000000
--- a/tests/e2e/test/ermine_session_shell_simple_browser_test.dart
+++ /dev/null
@@ -1,771 +0,0 @@
-// Copyright 2020 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// ignore_for_file: import_of_legacy_library_into_null_safe
-import 'dart:async';
-import 'dart:math';
-
-import 'package:ermine_driver/ermine_driver.dart';
-import 'package:ermine_driver/simple_browser_driver.dart';
-import 'package:fidl_fuchsia_input/fidl_async.dart';
-import 'package:fidl_fuchsia_ui_input3/fidl_async.dart' hide KeyEvent;
-import 'package:flutter_driver/flutter_driver.dart';
-import 'package:gcloud_lib/gcloud_lib.dart';
-import 'package:sl4f/sl4f.dart';
-import 'package:test/test.dart';
-import 'package:webdriver/sync_io.dart';
-
-const _timeoutOneSec = Duration(seconds: 1);
-const _timeoutThreeSec = Duration(seconds: 3);
-const _timeoutTenSec = Duration(seconds: 10);
-const _timeoutPerTest = Timeout(Duration(seconds: 60));
-const _sampleViewRect = Rectangle(100, 200, 100, 100);
-const testserverUrl =
-    'fuchsia-pkg://fuchsia.com/ermine_testserver#meta/ermine_testserver.cmx';
-
-// Flags to enable/disable each test in order of
-// 0: Web pages & history navigation test
-// 1: Video test
-// 2: Tab control test
-// 3: Text input field test
-// 4: Audio test
-// 5: Keyboard shortcut test.
-const skipTests = [true, true, false, true, true, true];
-
-void main() {
-  late Sl4f sl4f;
-  late ErmineDriver ermine;
-  late WebDriverConnector webDriverConnector;
-  late Input input;
-
-  // TODO(fxb/69334): Get rid of the space in the hint text.
-  const newTabHintText = '     SEARCH';
-  const indexUrl = 'http://127.0.0.1:8080/index.html';
-  const stopUrl = 'http://127.0.0.1:8080/stop';
-  final newTabFinder = find.text('NEW TAB');
-  final indexTabFinder = find.text('Localhost');
-  final nextTabFinder = find.text('Next Page');
-  final popupTabFinder = find.text('Popup Page');
-  final videoTabFinder = find.text('Video Test');
-  final redTabFinder = find.text('Red Page');
-  final greenTabFinder = find.text('Green Page');
-  final blueTabFinder = find.text('Blue Page');
-  final audioTabFinder = find.text('Audio Test');
-
-  setUpAll(() async {
-    sl4f = Sl4f.fromEnvironment();
-    await sl4f.startServer();
-    print('Started Sl4f server');
-
-    ermine = ErmineDriver(sl4f);
-    await ermine.setUp();
-    print('Set up Ermine driver');
-
-    input = Input(sl4f);
-    print('Set up Input');
-
-    webDriverConnector = WebDriverConnector('runtime_deps/chromedriver', sl4f);
-    await webDriverConnector.initialize();
-    print('Set up and initialized a web driver');
-  });
-
-  tearDownAll(() async {
-    await webDriverConnector.tearDown();
-    print('Tore down Web driver');
-    await Future.delayed(Duration(seconds: 1));
-    await ermine.tearDown();
-    print('Tore down Ermine flutter driver');
-    await sl4f.stopServer();
-    print('Stopped sl4f server');
-    sl4f.close();
-    print('Closed sl4f');
-  });
-
-  Future<bool> _waitForTabArrangement(FlutterDriver browser,
-      SerializableFinder leftTabFinder, SerializableFinder rightTabFinder,
-      {Duration timeout = const Duration(seconds: 30)}) async {
-    return ermine.waitFor(() async {
-      final leftTabX = (await browser.getCenter(leftTabFinder)).dx;
-      final rightTabX = (await browser.getCenter(rightTabFinder)).dx;
-      return leftTabX < rightTabX;
-    }, timeout: timeout);
-  }
-
-  /// Keeps finding a web element that satisfies the given [by] condition until
-  /// the timeout expires. [NoSuchElementException] thrown by [findElement]
-  /// in the meantime is ignored.
-  /// Returns the [WebElement] if it finds one. Otherwise, returns null.
-  Future<WebElement?> _waitForWebElement(WebDriver web, By by) async {
-    return await ermine.waitFor<WebElement?>(() async {
-      try {
-        final element = web.findElement(by);
-        return element;
-      } on NoSuchElementException {
-        return null;
-      }
-    }, timeout: _timeoutTenSec);
-  }
-
-  /// Keeps calling the given action until it gets the expected result within
-  /// a fixed time. Errors thrown by the action in the meantime is ignored.
-  /// e.g. [DriverError], thrown in case the driver fails to locate a [Finder].
-  /// Returns true if it gets the expected result. Otherwise, returns false.
-  Future<bool> _repeatActionUntilGetResult(
-      dynamic action(), Future<void> result()) async {
-    return await ermine.waitFor(() async {
-      action.call();
-      try {
-        await result.call();
-        return true;
-        // ignore: avoid_catches_without_on_clauses
-      } catch (e) {
-        print('$e. Keep repeating the action until timeout expires...');
-        return false;
-      }
-    }, timeout: _timeoutTenSec);
-  }
-
-  /// Keeps calling `waitFor(finder)` until the timeout expires. Returns true
-  /// if it locates one. Otherwise, returns false.
-  Future<bool> _repeatActionWaitingFor(
-    FlutterDriver browser,
-    dynamic action(),
-    SerializableFinder finder, {
-    Duration waitForTimeout = _timeoutOneSec,
-  }) async {
-    return await _repeatActionUntilGetResult(
-        action, () => browser.waitFor(finder, timeout: waitForTimeout));
-  }
-
-  /// Keeps calling `waitForAbsent(finder)` until the timeout expires. Returns
-  /// true if it locates one. Otherwise, returns false.
-  Future<bool> _repeatActionWaitingForAbsent(
-    FlutterDriver browser,
-    dynamic action(),
-    SerializableFinder finder, {
-    Duration waitForAbsentTimeout = _timeoutOneSec,
-  }) async {
-    return await _repeatActionUntilGetResult(action,
-        () => browser.waitForAbsent(finder, timeout: waitForAbsentTimeout));
-  }
-
-  Future<void> _invokeShortcut(List<Key> keys) async {
-    const pressDuration = 100;
-    final releaseDuration = pressDuration * keys.length + 100;
-    var pressDurations = [
-      for (var i = 0; i < keys.length; i++) pressDuration + (i * 100)
-    ];
-    var releaseDurations = [
-      for (var i = 0; i < keys.length; i++) releaseDuration + (i * 100)
-    ];
-
-    await input.keyEvents([
-      for (var i = 0; i < keys.length; i++)
-        KeyEvent(keys[i], Duration(milliseconds: pressDurations[i]),
-            KeyEventType.pressed),
-      // Releases the key in reverse order.
-      for (var i = 0; i < keys.length; i++)
-        KeyEvent(keys[keys.length - i - 1],
-            Duration(milliseconds: releaseDurations[i]), KeyEventType.released)
-    ]);
-
-    await ermine.driver
-        .waitUntilNoTransientCallbacks(timeout: Duration(seconds: 2));
-  }
-
-  // TODO(fxb/68689): Transition pointer interactions to Sl4f.Input once it is
-  // ready.
-  test('Should be able to do page and history navigation.', () async {
-    // Starts hosting a local http website.
-    // ignore: unawaited_futures
-    ermine.component.launch(testserverUrl);
-    print('Launched the test server .');
-
-    FlutterDriver browser;
-    browser = await ermine.launchAndWaitForSimpleBrowser();
-
-    // Access to the website.
-    await input.text(indexUrl);
-    print('Typed in $indexUrl to the browser');
-    await input.keyPress(kEnterKey);
-    print('Pressed Enter');
-    await browser.waitUntilFirstFrameRasterized();
-    await browser.waitUntilNoTransientCallbacks(timeout: _timeoutTenSec);
-    await browser.waitFor(indexTabFinder, timeout: _timeoutTenSec);
-    print('Index tab found.');
-
-    final webdriver =
-        (await webDriverConnector.webDriversForHost('127.0.0.1')).single;
-    print('Connected a web driver to the localhost');
-
-    expect(await browser.getText(indexTabFinder), isNotNull);
-    print('Opened $indexUrl');
-
-    final nextLink = await _waitForWebElement(webdriver, By.linkText('Next'));
-    expect(nextLink, isNotNull);
-
-    // Clicks the text link that opens next.html (page navigation)
-    expect(
-        await _repeatActionWaitingForAbsent(
-            browser, nextLink!.click, indexTabFinder),
-        isTrue,
-        reason: 'Failed to click the Next link.');
-    expect(await browser.getText(newTabFinder), isNotNull);
-    expect(await browser.getText(nextTabFinder), isNotNull);
-    print('Clicked the next.html link');
-
-    final prevLink = await _waitForWebElement(webdriver, By.linkText('Prev'));
-    expect(prevLink, isNotNull);
-
-    // Clicks the text link that opens index.html (page navigation)
-    expect(
-        await _repeatActionWaitingForAbsent(
-            browser, prevLink!.click, nextTabFinder),
-        isTrue,
-        reason: 'Failed to click the Prev link.');
-
-    expect(await browser.getText(newTabFinder), isNotNull);
-    expect(await browser.getText(indexTabFinder), isNotNull);
-    print('Clicked the index.html link');
-
-    // Goes back to next.html by tapping the BCK button (history navigation)
-    expect(
-      await _repeatActionWaitingForAbsent(browser, () async {
-        final back = find.byValueKey('back');
-        await browser.tap(back);
-      }, indexTabFinder),
-      isTrue,
-      reason: 'Failed to hit the BCK button.',
-    );
-
-    expect(await browser.getText(newTabFinder), isNotNull);
-    expect(await browser.getText(nextTabFinder), isNotNull);
-    print('Hit BCK');
-
-    // Goes forward to index.html by tapping the FWD button (history navigation)
-    expect(
-      await _repeatActionWaitingForAbsent(browser, () async {
-        final forward = find.byValueKey('forward');
-        await browser.tap(forward);
-      }, nextTabFinder),
-      isTrue,
-      reason: 'Failed to hit the FWD button.',
-    );
-
-    expect(await browser.getText(newTabFinder), isNotNull);
-    expect(await browser.getText(indexTabFinder), isNotNull);
-    print('Hit FWD');
-
-    // Clicks + button to increase the number
-    var digitLink = await _waitForWebElement(webdriver, By.id('target'));
-    final addButton = await _waitForWebElement(webdriver, By.id('increase'));
-    expect(digitLink!.text, '0');
-    addButton!.click();
-    await ermine.waitFor(() async {
-      return digitLink!.text == '1';
-    });
-    addButton.click();
-    await ermine.waitFor(() async {
-      return digitLink!.text == '2';
-    });
-    print('Clicked the + button next to the digit three times');
-
-    // Refreshes the page
-    final refresh = find.byValueKey('refresh');
-    await browser.tap(refresh);
-    await browser.waitUntilNoTransientCallbacks(timeout: _timeoutTenSec);
-    digitLink = await _waitForWebElement(webdriver, By.id('target'));
-    await ermine.waitFor(() async {
-      return digitLink!.text == '0';
-    });
-    print('Hit RFRSH');
-
-    final popupLink = await _waitForWebElement(webdriver, By.linkText('Popup'));
-    expect(popupLink, isNotNull);
-
-    // Clicks the text link that opens popup.html (popup page navigation)
-    expect(
-        await _repeatActionWaitingFor(browser, popupLink!.click, popupTabFinder,
-            waitForTimeout: _timeoutThreeSec),
-        isTrue,
-        reason: 'Failed to click the Popup link.');
-    expect(await browser.getText(newTabFinder), isNotNull);
-    expect(await browser.getText(indexTabFinder), isNotNull);
-    expect(await browser.getText(popupTabFinder), isNotNull);
-    print('Clicked the popup.html link');
-
-    // Stops the local http server.
-    await browser.requestData(stopUrl);
-    await browser.waitUntilNoTransientCallbacks(timeout: _timeoutTenSec);
-    await browser.waitFor(find.text(stopUrl), timeout: _timeoutTenSec);
-
-    // Closes the flutter driver connected to the browser.
-    await browser.close();
-
-    // Close the simple browser view.
-    await ermine.threeKeyShortcut(Key.leftCtrl, Key.leftShift, Key.w);
-    await ermine.driver.waitUntilNoTransientCallbacks(timeout: _timeoutTenSec);
-    await ermine.waitForViewAbsent(simpleBrowserUrl);
-    print('Closed the browser');
-  }, timeout: _timeoutPerTest, skip: skipTests[0]);
-
-  test('Should be able to play videos on web pages.', () async {
-    // Starts hosting a local http website.
-    // ignore: unawaited_futures
-    ermine.component.launch(testserverUrl);
-    print('Launched the test server.');
-
-    FlutterDriver browser;
-    browser = await ermine.launchAndWaitForSimpleBrowser();
-
-    // Access to video.html where the following video is played:
-    // experiences/bin/ermine_testserver/public/simple_browser_test/sample_video.mp4
-    // It shows the violet-colored background for the first 3 seconds then shows
-    // the fuchsia-colored background for another 3 seconds.
-    await input.text('http://127.0.0.1:8080/video.html');
-    await input.keyPress(kEnterKey);
-    await browser.waitFor(videoTabFinder, timeout: _timeoutTenSec);
-
-    expect(await browser.getText(videoTabFinder), isNotNull);
-    print('Opened http://127.0.0.1:8080/video.html');
-
-    // Waits for a while for the video to be loaded before taking a screenshot.
-    await Future.delayed(Duration(seconds: 2));
-    final earlyScreenshot = await ermine.screenshot(_sampleViewRect);
-
-    // Takes another screenshot after 3 seconds.
-    await Future.delayed(Duration(seconds: 3));
-
-    final isVideoPlayed = await ermine.waitFor(() async {
-      final lateScreenshot = await ermine.screenshot(_sampleViewRect);
-      final diff = ermine.screenshotsDiff(earlyScreenshot, lateScreenshot);
-      return diff == 1;
-    }, timeout: _timeoutTenSec);
-
-    expect(isVideoPlayed, isTrue);
-    print('The video was played');
-
-    // Stops the local http server.
-    await browser.requestData(stopUrl);
-    await browser.waitUntilNoTransientCallbacks(timeout: _timeoutTenSec);
-    await browser.waitFor(find.text(stopUrl), timeout: _timeoutTenSec);
-
-    // Closes the flutter driver connected to the browser.
-    await browser.close();
-    await ermine.driver.requestData('close');
-    await ermine.driver.waitForAbsent(find.text('simple-browser.cmx'));
-    expect(await ermine.isStopped(simpleBrowserUrl), isTrue);
-    print('Closed the browser');
-  }, timeout: _timeoutPerTest, skip: skipTests[1]);
-
-  test('Should be able to switch, rearrange, and close tabs', () async {
-    // Starts hosting a local http website.
-    expect(await ermine.launch(testserverUrl), isTrue);
-    await ermine.driver.waitUntilNoTransientCallbacks();
-    print('Launched the test server.');
-
-    final browser = SimpleBrowserDriver(ermine);
-    await browser.launchAndWaitForSimpleBrowser();
-
-    /// Tab Switching Test
-    const redUrl = 'http://127.0.0.1:8080/red.html';
-    const greenUrl = 'http://127.0.0.1:8080/green.html';
-    const blueUrl = 'http://127.0.0.1:8080/blue.html';
-
-    // Opens red.html in the second tab leaving the first tab as an empty tab.
-    await input.text(redUrl, keyEventDuration: Duration(milliseconds: 50));
-    print('Typed in $redUrl to the browser');
-    await input.keyPress(kEnterKey);
-    print('Pressed Enter');
-    await browser.driver.waitUntilFirstFrameRasterized();
-    await browser.driver.waitUntilNoTransientCallbacks(timeout: _timeoutTenSec);
-    await browser.driver.waitFor(redTabFinder, timeout: _timeoutTenSec);
-    print('Opened red.html');
-
-    // Opens green.html in the third tab.
-    await browser.driver.tap(find.byValueKey('new_tab'));
-    await browser.driver
-        .waitFor(find.text(newTabHintText), timeout: _timeoutTenSec);
-
-    await input.text(greenUrl, keyEventDuration: Duration(milliseconds: 50));
-    print('Typed in $greenUrl to the browser');
-    await input.keyPress(kEnterKey);
-    print('Pressed Enter');
-    await browser.driver.waitUntilFirstFrameRasterized();
-    await browser.driver.waitUntilNoTransientCallbacks(timeout: _timeoutTenSec);
-    await browser.driver.waitFor(greenTabFinder, timeout: _timeoutTenSec);
-    print('Opened green.html');
-
-    // Opens blue.html in the forth tab.
-    await browser.driver.tap(find.byValueKey('new_tab'));
-    await browser.driver
-        .waitFor(find.text(newTabHintText), timeout: _timeoutTenSec);
-
-    await input.text(blueUrl, keyEventDuration: Duration(milliseconds: 50));
-    print('Typed in $blueUrl to the browser');
-    await input.keyPress(kEnterKey);
-    print('Pressed Enter');
-    await browser.driver.waitUntilFirstFrameRasterized();
-    await browser.driver.waitUntilNoTransientCallbacks(timeout: _timeoutTenSec);
-    await browser.driver.waitFor(blueTabFinder, timeout: _timeoutTenSec);
-    print('Opened blue.html');
-
-    // Should have 4 tabs and the forth tab should be focused.
-    expect(await browser.driver.getText(newTabFinder), isNotNull);
-    expect(await browser.driver.getText(redTabFinder), isNotNull);
-    expect(await browser.driver.getText(greenTabFinder), isNotNull);
-    expect(await browser.driver.getText(blueTabFinder), isNotNull);
-    expect(await browser.driver.getText(find.text(blueUrl)), isNotNull);
-    print('The Blue tab is focused');
-
-    // The second tab should be focused when tapped.
-    await browser.driver.tap(redTabFinder);
-    await browser.driver.waitFor(find.text(redUrl));
-    expect(await browser.driver.getText(find.text(redUrl)), isNotNull);
-    print('Clicked the Red tab');
-
-    // The thrid tab should be focused when tapped.
-    await browser.driver.tap(greenTabFinder);
-    await browser.driver.waitFor(find.text(greenUrl));
-    expect(await browser.driver.getText(find.text(greenUrl)), isNotNull);
-    print('Clicked the Green tab');
-
-    /// Tab Rearranging Test
-
-    // Checks the current order of tabs before rearranging tabs.
-    expect(
-        await _waitForTabArrangement(
-            browser.driver, newTabFinder, redTabFinder),
-        isTrue,
-        reason: 'The New tab is not on the left side of the Red tab:');
-    expect(
-        await _waitForTabArrangement(
-            browser.driver, redTabFinder, greenTabFinder),
-        isTrue,
-        reason: 'The Red tab is not on the left side of the Green tab');
-    expect(
-        await _waitForTabArrangement(
-            browser.driver, greenTabFinder, blueTabFinder),
-        isTrue,
-        reason: 'The Green tab is not on the left side of the Blue tab');
-    print('The tabs are in the order of New > Red > Green > Blue');
-
-    // Drags the second tab to the right end of the tab list.
-    await browser.driver.scroll(redTabFinder, 600, 0, Duration(seconds: 1));
-
-    // The order of tabs after rearranging tabs.
-    expect(
-        await _waitForTabArrangement(
-            browser.driver, newTabFinder, greenTabFinder),
-        isTrue,
-        reason: 'The New tab is not on the left side of the Green tab.');
-    expect(
-        await _waitForTabArrangement(
-            browser.driver, greenTabFinder, blueTabFinder),
-        isTrue,
-        reason: 'The Green tab is not on the left side of the Blue tab');
-    expect(
-        await _waitForTabArrangement(
-            browser.driver, blueTabFinder, redTabFinder),
-        isTrue,
-        reason: 'The Blue tab is not on the left side of the Red tab');
-    print('Moved the Red tab to the right end');
-
-    /// Tab closing test
-    final tabCloseFinder = find.byValueKey('tab_close');
-    await browser.driver.tap(tabCloseFinder);
-
-    // The red page should be gone and the last tab should be focused.
-    await browser.driver.waitForAbsent(redTabFinder);
-    print('Closed the Red tab');
-
-    expect(await browser.driver.getText(newTabFinder), isNotNull);
-    expect(await browser.driver.getText(greenTabFinder), isNotNull);
-    expect(await browser.driver.getText(blueTabFinder), isNotNull);
-    expect(await browser.driver.getText(find.text(blueUrl)), isNotNull);
-    print('The Blue tab is focused');
-
-    // TODO(fxb/70265): Test closing an unfocused tab once fxb/68689 is done.
-
-    await ermine.threeKeyShortcut(Key.leftCtrl, Key.leftShift, Key.w);
-    await ermine.driver.waitUntilNoTransientCallbacks();
-    await ermine.waitForViewAbsent(simpleBrowserUrl);
-    print('Closed the browser');
-
-    await ermine.threeKeyShortcut(Key.leftCtrl, Key.leftShift, Key.w);
-    await ermine.driver.waitUntilNoTransientCallbacks();
-    await ermine.waitForViewAbsent(testserverUrl);
-    print('Closed the test server');
-
-    // Closes the flutter driver connected to the browser.
-    await browser.tearDown();
-    print('Tore down the browser driver');
-  }, timeout: _timeoutPerTest, skip: skipTests[2]);
-
-  test('Should be able enter text into web text fields', () async {
-    // Starts hosting a local http website.
-    expect(await ermine.launch(testserverUrl), isTrue);
-    await ermine.driver.waitUntilNoTransientCallbacks();
-    print('Launched the test server.');
-
-    FlutterDriver browser;
-    browser = await ermine.launchAndWaitForSimpleBrowser();
-
-    const testInputPage = 'http://127.0.0.1:8080/input.html';
-    final textInputTabFinder = find.text('Text Input');
-
-    // Access to the website.
-    await input.text(testInputPage);
-    await input.keyPress(kEnterKey);
-    await browser.waitUntilNoTransientCallbacks(timeout: _timeoutTenSec);
-    await browser.waitFor(textInputTabFinder, timeout: _timeoutTenSec);
-    print('Opened $testInputPage');
-
-    final webdriver =
-        (await webDriverConnector.webDriversForHost('127.0.0.1')).single;
-
-    final textField = await _waitForWebElement(webdriver, By.id('text-input'));
-    print('The textfield is found.');
-
-    expect(textField, isNotNull);
-
-    textField!.click();
-    await ermine.waitFor(() async {
-      return webdriver.activeElement!.equals(textField);
-    }, timeout: _timeoutTenSec);
-
-    print('The textfield is now focused.');
-
-    // TODO(fxb/74070): Sl4f.Input currently does not work for web elements.
-    // Replace the following line with Sl4f.Input once that is fixed.
-    const testText = 'hello fuchsia';
-    textField.sendKeys(testText);
-    await ermine.waitFor(() async {
-      return textField.properties['value'] == testText;
-    }, timeout: _timeoutTenSec);
-    print('Text is entered into the textfield.');
-
-    // Closes the flutter driver connected to the browser.
-    await browser.close();
-
-    await ermine.driver.requestData('closeAll');
-    print('Closed all views');
-  }, timeout: _timeoutPerTest, skip: skipTests[3]);
-
-  test('Should be able to play audios on web', () async {
-    // Starts hosting a local http website.
-    // ignore: unawaited_futures
-    ermine.component.launch(testserverUrl);
-    print('Launched the test server.');
-
-    FlutterDriver browser;
-    browser = await ermine.launchAndWaitForSimpleBrowser();
-
-    final record = Audio(sl4f);
-    final gcloud = GCloud();
-
-    // Access to audio.html where the following audio is played:
-    // experiences/bin/ermine_testserver/public/simple_browser_test/sample_audio.mp3
-    // It plays human voice saying "How old is Obama".
-    await input.text('http://127.0.0.1:8080/audio.html');
-    await input.keyPress(kEnterKey);
-    await browser.waitFor(audioTabFinder, timeout: _timeoutTenSec);
-
-    expect(await browser.getText(audioTabFinder), isNotNull);
-    print('Opened http://127.0.0.1:8080/audio.html');
-
-    final webdriver =
-        (await webDriverConnector.webDriversForHost('127.0.0.1')).single;
-
-    final audio = await _waitForWebElement(webdriver, By.id('audio'));
-    expect(audio, isNotNull);
-    print('The textfield is found.');
-
-    final playButton = await _waitForWebElement(webdriver, By.id('play'));
-    expect(playButton, isNotNull);
-    print('The PLAY button is found.');
-
-    // Note that it doesn't work locally. You should create GCloud using
-    // `GCloud.withClientViaApiKey()` with an API key for local testing.
-    await gcloud.setClientFromMetadata();
-    print('Set an authenticated gcloud client');
-
-    // Plays the audio, records it, sends it to gcloud for speech-to-text, and
-    // verifies if the text result is what we expect.
-    // Retries this process for a few more times if it fails since the audio
-    // sometimes sounds janky.
-    final ttsResult = await ermine.waitFor(() async {
-      print('Start recording audio.');
-      await record.startOutputSave();
-      await Future.delayed(_timeoutOneSec);
-      playButton!.click();
-
-      // Waits for the audio being played to the end.
-      await Future.delayed(Duration(seconds: 5));
-
-      await record.stopOutputSave();
-      final audioOutput = await record.getOutputAudio();
-      print('Stopped recording audio.');
-
-      final ttsList =
-          await speechToText(gcloud.speech, audioOutput.audioData, 'en-us');
-      final tts = ttsList.first.toLowerCase();
-      print('STT result: $tts');
-      return tts == 'how old is obama';
-    });
-
-    expect(ttsResult, isTrue);
-
-    gcloud.close();
-
-    // Stops the local http server.
-    await browser.requestData(stopUrl);
-    await browser.waitUntilNoTransientCallbacks(timeout: _timeoutTenSec);
-    await browser.waitFor(find.text(stopUrl), timeout: _timeoutTenSec);
-
-    // Closes the flutter driver connected to the browser.
-    await browser.close();
-    await ermine.driver.requestData('close');
-    await ermine.driver.waitForAbsent(find.text('simple-browser.cmx'));
-    expect(await ermine.isStopped(simpleBrowserUrl), isTrue);
-    print('Closed the browser');
-  }, timeout: _timeoutPerTest, skip: skipTests[4]);
-
-  test('Should be able to control the browser with keyboard shortcuts',
-      () async {
-    // Starts hosting a local http website.
-    // ignore: unawaited_futures
-    ermine.component.launch(testserverUrl);
-    print('Launched the test server.');
-
-    FlutterDriver browser;
-    browser = await ermine.launchAndWaitForSimpleBrowser();
-
-    // Opens index.html
-    await input.text(indexUrl, keyEventDuration: Duration(milliseconds: 10));
-    await input.keyPress(kEnterKey);
-    await browser.waitUntilNoTransientCallbacks(timeout: _timeoutTenSec);
-    await browser.waitFor(indexTabFinder, timeout: _timeoutTenSec);
-
-    final webdriver =
-        (await webDriverConnector.webDriversForHost('127.0.0.1')).single;
-    expect(await browser.getText(newTabFinder), isNotNull);
-    expect(await browser.getText(indexTabFinder), isNotNull);
-    print('Opened $indexUrl');
-
-    // Clicks the + buttons
-    var digitLink = await _waitForWebElement(webdriver, By.id('target'));
-    final addButton = await _waitForWebElement(webdriver, By.id('increase'));
-    expect(digitLink!.text, '0');
-    addButton!.click();
-    await ermine.waitFor(() async {
-      return digitLink!.text == '1';
-    });
-    addButton.click();
-    await ermine.waitFor(() async {
-      return digitLink!.text == '2';
-    });
-    print('Clicked the + button next to the digit three times');
-
-    // Shortcut for refresh (Ctrl + r)
-    await _invokeShortcut([Key.leftCtrl, Key.r]);
-    digitLink = await _waitForWebElement(webdriver, By.id('target'));
-    await ermine.waitFor(() async {
-      return digitLink!.text == '0';
-    });
-    print('Refreshed the page');
-
-    // Clicks the 'Next' link
-    final nextLink = await _waitForWebElement(webdriver, By.linkText('Next'));
-    expect(nextLink, isNotNull);
-    expect(
-        await _repeatActionWaitingForAbsent(
-            browser, nextLink!.click, indexTabFinder),
-        isTrue,
-        reason: 'Failed to click the Next link.');
-    expect(await browser.getText(newTabFinder), isNotNull);
-    expect(await browser.getText(nextTabFinder), isNotNull);
-    print('Clicked the next.html link');
-
-    // Shortcut for backward (Alt + ←)
-    expect(
-        await _repeatActionWaitingForAbsent(
-            browser,
-            () async => await _invokeShortcut([Key.leftAlt, Key.left]),
-            nextTabFinder),
-        isTrue,
-        reason: 'Failed to invoke the shortcut for navigating back.');
-    expect(await browser.getText(newTabFinder), isNotNull);
-    expect(await browser.getText(indexTabFinder), isNotNull);
-    print('Navigated back to index.html');
-
-    // Shortcut for forward (Alt + →)
-    expect(
-        await _repeatActionWaitingForAbsent(
-            browser,
-            () async => await _invokeShortcut([Key.leftAlt, Key.right]),
-            nextTabFinder),
-        isTrue,
-        reason: 'Failed to invoke the shortcut for navigating forward.');
-    expect(await browser.getText(newTabFinder), isNotNull);
-    expect(await browser.getText(indexTabFinder), isNotNull);
-    print('Navigated forward to next.html');
-
-    // Shortcut for opening a new tab (Ctrl + t)
-    await _invokeShortcut([Key.leftCtrl, Key.t]);
-    await browser.waitFor(find.text(newTabHintText), timeout: _timeoutTenSec);
-
-    // Opens blue.html
-    const blueUrl = 'http://127.0.0.1:8080/blue.html';
-    const nextUrl = 'http://127.0.0.1:8080/next.html';
-    await input.text(blueUrl, keyEventDuration: Duration(milliseconds: 10));
-    await input.keyPress(kEnterKey);
-    await browser.waitUntilNoTransientCallbacks(timeout: _timeoutTenSec);
-    await browser.waitFor(blueTabFinder, timeout: _timeoutTenSec);
-    print('Opened blue.html');
-
-    // Shortcut for selecting the next tab (Ctrl + Tab) x 2
-    await _invokeShortcut([Key.leftCtrl, Key.tab]);
-    await browser.waitFor(find.text(newTabHintText));
-    expect(await browser.getText(find.text(newTabHintText)), isNotNull);
-    print('The new tab is now selected');
-
-    await _invokeShortcut([Key.leftCtrl, Key.tab]);
-    await browser.waitFor(find.text(nextUrl));
-    expect(await browser.getText(find.text(nextUrl)), isNotNull);
-    print('The next tab is now selected');
-
-    // Shortcut for selecting the previous tab (Ctrl + Shift +Tab) x 2
-    await _invokeShortcut([Key.leftCtrl, Key.leftShift, Key.tab]);
-    await browser.waitFor(find.text(newTabHintText));
-    expect(await browser.getText(find.text(newTabHintText)), isNotNull);
-    print('The new tab is now selected');
-
-    await _invokeShortcut([Key.leftCtrl, Key.leftShift, Key.tab]);
-    await browser.waitFor(find.text(blueUrl));
-    expect(await browser.getText(find.text(blueUrl)), isNotNull);
-    print('The blue tab is now selected');
-
-    // Shortcut for closing a current tab (Ctrl + w)
-    await _invokeShortcut([Key.leftCtrl, Key.w]);
-    await browser.waitForAbsent(blueTabFinder);
-    print('Closed the blue tab');
-
-    expect(await browser.getText(newTabFinder), isNotNull);
-    expect(await browser.getText(find.text(nextUrl)), isNotNull);
-    print('The index tab is focused');
-
-    // Stops the local http server.
-    await browser.requestData(stopUrl);
-    await browser.waitUntilNoTransientCallbacks(timeout: _timeoutTenSec);
-    await browser.waitFor(find.text(stopUrl), timeout: _timeoutTenSec);
-
-    // Closes the flutter driver connected to the browser.
-    await browser.close();
-    await ermine.threeKeyShortcut(Key.leftCtrl, Key.leftShift, Key.w);
-    await ermine.driver.waitUntilNoTransientCallbacks(timeout: _timeoutTenSec);
-    await ermine.waitForViewAbsent(simpleBrowserUrl);
-    print('Closed the browser');
-  }, timeout: _timeoutPerTest, skip: skipTests[5]);
-}
diff --git a/tests/e2e/test/ermine_shell_test.dart b/tests/e2e/test/ermine_shell_test.dart
index 097bca9..37c0175 100644
--- a/tests/e2e/test/ermine_shell_test.dart
+++ b/tests/e2e/test/ermine_shell_test.dart
@@ -63,14 +63,13 @@
     // Launch terminal.
     const terminalUrl = 'fuchsia-pkg://fuchsia.com/terminal#meta/terminal.cmx';
     await ermine.launch(terminalUrl);
-    await ermine.waitForView(terminalUrl);
+    await ermine.waitForView(terminalUrl, testForFocus: true);
 
     // Launch spinning_square_view, it should have focus.
     const spinningSquareViewUrl =
         'fuchsia-pkg://fuchsia.com/spinning_square_view#meta/spinning_square_view.cmx';
     await ermine.launch(spinningSquareViewUrl);
-    var view = await ermine.waitForView(spinningSquareViewUrl);
-    expect(view.focused, isTrue);
+    await ermine.waitForView(spinningSquareViewUrl, testForFocus: true);
 
     // Tap on terminal to switch focus to it. Terminal view should be left half
     // of the screen. [input.tap] assumes screen resolution as 1000 x 1000.
@@ -78,7 +77,6 @@
     await ermine.driver.waitUntilNoTransientCallbacks();
 
     // Terminal should now have focus.
-    view = await ermine.waitForView(terminalUrl);
-    expect(view.focused, isTrue);
+    await ermine.waitForView(terminalUrl, testForFocus: true);
   }, skip: true);
 }
diff --git a/tests/e2e/test/ermine_smoke_test.dart b/tests/e2e/test/ermine_smoke_test.dart
index f4a312a..fd74831 100644
--- a/tests/e2e/test/ermine_smoke_test.dart
+++ b/tests/e2e/test/ermine_smoke_test.dart
@@ -5,6 +5,7 @@
 // ignore_for_file: import_of_legacy_library_into_null_safe
 
 @Retry(2)
+@Timeout(Duration(minutes: 2))
 
 import 'package:ermine_driver/ermine_driver.dart';
 import 'package:fidl_fuchsia_input/fidl_async.dart';
@@ -28,7 +29,6 @@
   });
 
   tearDownAll(() async {
-    // Any of these may end up being null if the test fails in setup.
     await ermine.tearDown();
     await sl4f.stopServer();
     sl4f.close();
@@ -49,6 +49,7 @@
   test('Text input, pointer input and keyboard shortcut', () async {
     print('Launching terminal...');
     final terminalFinder = find.text('Terminal');
+    await ermine.driver.waitUntilNoTransientCallbacks();
     final appResult = await ermine.driver.getText(terminalFinder);
     expect(appResult, 'Terminal');
 
@@ -66,6 +67,21 @@
     print('Verifying Ctrl+Shift+w shortcut is closing terminal');
     await ermine.threeKeyShortcut(Key.leftCtrl, Key.leftShift, Key.w);
     await ermine.driver.waitUntilNoTransientCallbacks();
+    await ermine.waitForAction('close');
     expect(await ermine.isStopped(terminalUrl), isTrue);
-  });
+
+    // Get the current value of dark mode.
+    bool darkMode = (await ermine.snapshot).darkMode;
+
+    // Toggle it.
+    await ermine.driver.tap(find.byValueKey('darkMode'));
+    expect((await ermine.snapshot).darkMode, !darkMode);
+
+    // Logout from ermine.
+    print('Logging out and back in');
+    await ermine.logoutAndLogin();
+
+    // Dark mode toggle should have persisted across auth flows.
+    expect((await ermine.snapshot).darkMode, !darkMode);
+  }, timeout: Timeout(Duration(minutes: 2)));
 }
diff --git a/tests/e2e/test/ermine_terminal_test.dart b/tests/e2e/test/ermine_terminal_test.dart
index 7df0a04..b0594a9 100644
--- a/tests/e2e/test/ermine_terminal_test.dart
+++ b/tests/e2e/test/ermine_terminal_test.dart
@@ -53,7 +53,10 @@
       var views = await ermine.launchedViews(filterByUrl: componentUrl);
       if (views.length == instances) {
         if (testForFocus) {
-          expect(views.any((view) => view.focused), isTrue);
+          // Wait for a view with focus.
+          if (!views.any((view) => view.focused)) {
+            return null;
+          }
         }
         return views;
       }
diff --git a/tests/lib/ermine_driver.dart b/tests/lib/ermine_driver.dart
index 189385c..f277297 100644
--- a/tests/lib/ermine_driver.dart
+++ b/tests/lib/ermine_driver.dart
@@ -17,10 +17,14 @@
 import 'package:sl4f/sl4f.dart';
 import 'package:test/test.dart';
 
-const ermineUrl = 'fuchsia-pkg://fuchsia.com/ermine#meta/ermine.cmx';
 const simpleBrowserUrl =
     'fuchsia-pkg://fuchsia.com/simple-browser#meta/simple-browser.cmx';
 const terminalUrl = 'fuchsia-pkg://fuchsia.com/terminal#meta/terminal.cmx';
+const stashCtlUrl = 'fuchsia-pkg://fuchsia.com/stash_ctl#meta/stash_ctl.cmx';
+const kLoginInspectSelector =
+    'core/session-manager/session\\:session/workstation_session/login_shell';
+const kErmineInspectSelector =
+    'core/session-manager/session\\:session/workstation_session/login_shell/ermine_shell';
 
 // USB HID code for ENTER key.
 // See <https://www.usb.org/sites/default/files/documents/hut1_12v2.pdf>
@@ -41,6 +45,7 @@
   final Component _component;
 
   FlutterDriver? _driver;
+  FlutterDriver? _login;
   final FlutterDriverConnector _connector;
 
   /// Constructor.
@@ -51,6 +56,9 @@
   /// The instance of [FlutterDriver] that is connected to Ermine flutter app.
   FlutterDriver get driver => _driver!;
 
+  /// The instance of [FlutterDriver] that is connected to Login flutter app.
+  FlutterDriver get login => _login!;
+
   /// The instance of [Component] that is connected to the DUT.
   Component get component => _component;
 
@@ -70,20 +78,17 @@
     await _connector.initialize();
     print('Flutter driver connector initialized');
 
-    // Now connect to ermine.
-    _driver = await _connector.driverForIsolate('ermine');
-    if (_driver == null) {
-      fail('Unable to connect to ermine.');
+    // Connect to login shell's flutter driver.
+    _login = await connectToFlutterDriver('login', kLoginInspectSelector,
+        (snapshot) => snapshot['ready'] == true);
+    if (_login == null) {
+      fail('Unable to connect to login shell.');
     }
+    print('Connected to login shell');
+
+    // Now connect to ermine.
+    _driver = await authenticate();
     print('Driver is connected to Ermine');
-
-    // Wait for shell to draw first frame.
-    await driver.waitUntilFirstFrameRasterized();
-    print('The first frame has been rasterized');
-
-    // Wait until rendering stabilizes and animations settle.
-    await driver.waitUntilNoTransientCallbacks();
-    print('No further transient callbacks. ErmineDriver is ready.');
   }
 
   /// Closes [FlutterDriverConnector] and performs cleanup.
@@ -279,8 +284,8 @@
     // Connects to the browser.
     // TODO(fxb/66577): Get the driver of the last isolate once it's supported by
     // [FlutterDriverConnector] in flutter_driver_sl4f.dart
-    final browserDriver =
-        await browserConnector.driverForIsolate('simple-browser');
+    final browserDriver = await browserConnector.driverForIsolateBySelector(
+        'simple-browser', 'simple-browser.cmx');
     // ignore: unnecessary_null_comparison
     if (browserDriver == null) {
       fail('unable to connect to simple browser.');
@@ -333,17 +338,24 @@
 
   Future<Rectangle> getViewRect(String viewUrl,
       [Duration timeout = waitForTimeout]) async {
-    final view = await waitForView(viewUrl, timeout);
+    final view = await waitForView(viewUrl, timeout: timeout);
     return view.viewport;
   }
 
   /// Finds the first launched component given its [viewUrl] and returns it's
-  /// Inspect data. Waits for [timeout] duration for view to launch.
+  /// Inspect data. Waits for [timeout] duration for view to launch. If
+  /// [testForFocus] is true, waits for focused signal.
   Future<ViewSnapshot> waitForView(String viewUrl,
-      [Duration timeout = waitForTimeout]) async {
+      {bool testForFocus = false, Duration timeout = waitForTimeout}) async {
     return waitFor(() async {
       final views = await launchedViews(filterByUrl: viewUrl);
-      return views.isNotEmpty ? views.first : null;
+      if (views.isEmpty) {
+        return null;
+      }
+      if (testForFocus) {
+        return views.first.focused ? views.first : null;
+      }
+      return views.first;
     }, timeout: timeout);
   }
 
@@ -355,12 +367,19 @@
     });
   }
 
-  Future<Map<String, dynamic>> inspectSnapshot(String componentSelector,
-      {Duration timeout = waitForTimeout}) {
+  Future<Map<String, dynamic>> inspectSnapshot(
+    String componentSelector, {
+    Duration timeout = waitForTimeout,
+    bool predicate(Map<String, dynamic> snapshot)?,
+  }) {
     return waitFor(() async {
       final snapshot = await Inspect(sl4f).snapshotRoot(componentSelector);
+      // Supply a default predicate that simply returns true.
+      predicate ??= (_) => true;
       // ignore: unnecessary_null_comparison
-      return snapshot == null || snapshot.isEmpty ? null : snapshot;
+      return snapshot == null || snapshot.isEmpty || !predicate!(snapshot)
+          ? null
+          : snapshot;
     }, timeout: timeout);
   }
 
@@ -370,6 +389,17 @@
     return ShellSnapshot(json.decode(data));
   }
 
+  /// Returns the last keyboard shortcut action received by ermine shell.
+  Future<String> get lastAction async => (await snapshot).lastAction;
+
+  /// Waits for last action to match the supplied value.
+  Future<bool> waitForAction(String action,
+      {Duration timeout = waitForTimeout}) async {
+    return waitFor(() async {
+      return (await lastAction) == action;
+    }, timeout: timeout);
+  }
+
   /// Returns the list of launched views from inspect data.
   Future<List<ViewSnapshot>> get views async => (await snapshot).views;
 
@@ -517,6 +547,141 @@
     // We ran out of time.
     throw TimeoutException('waitFor timeout expired', timeout);
   }
+
+  /// Connects to [FlutterDriver] for isolate [name] with [selector];
+  Future<FlutterDriver> connectToFlutterDriver(String name, String selector,
+      [bool predicate(Map<String, dynamic> snapshot)?]) async {
+    // Connect to Login flutter app.
+    print('Connecting to $name flutter isolate');
+    await inspectSnapshot(selector, predicate: predicate);
+    final driver = await _connector.driverForIsolateBySelector(name, selector);
+
+    // Wait for shell to draw first frame.
+    await driver.waitUntilFirstFrameRasterized();
+    print('The first frame has been rasterized');
+
+    // Wait until rendering stabilizes and animations settle.
+    await driver.waitUntilNoTransientCallbacks();
+    print('No further transient callbacks. $name is ready.');
+    return driver;
+  }
+
+  /// If the workstation build is enabled for authentication through the login
+  /// shell, go through the create password or login flow.
+  ///
+  /// Returns [true] is successfully performed authentication flows.
+  Future<FlutterDriver> authenticate() async {
+    // Check if login shell's inspect data is published and is 'ready'.
+    final snapshot = await inspectSnapshot(kLoginInspectSelector,
+        predicate: (snapshot) => snapshot['ready'] == true);
+
+    // Check if OOBE is skipped or shown.
+    if (snapshot['launchOOBE'] == true) {
+      // Check if auth status is authenticated. If no, start auth flow.
+      if (snapshot['authenticated'] != true) {
+        // Check if create password is visible.
+        print('Performing authentication...');
+
+        const testPassword = '11223344';
+        if (snapshot['screen'] == 'password') {
+          print('Creating password...');
+
+          // Tap on the first password text field.
+          var passwordTextField = find.byValueKey('password1');
+          await enterText(testPassword,
+              driver: login, textField: passwordTextField);
+          print('password 1 done');
+
+          // Tap on the second password text field.
+          passwordTextField = find.byValueKey('password2');
+          await enterText(testPassword,
+              driver: login, textField: passwordTextField);
+          print('password 2 done');
+
+          // Tap the Set Password button and wait for auth to succeed.
+          await login.tap(find.byValueKey('setPassword'));
+          await login.waitForAbsent(find.byType('Password'),
+              timeout: Duration(minutes: 2));
+          print('Password set');
+
+          // Tap through the 'Start Workstation' screen.
+          await login.tap(find.byValueKey('startWorkstation'));
+          await login.waitForAbsent(find.byType('Ready'));
+        } else {
+          print('Adding login credentials...');
+          // Tap on the password text field.
+          var passwordTextField = find.byValueKey('password');
+          await enterText(testPassword,
+              driver: login, textField: passwordTextField);
+          print('password entered');
+
+          // Tap the Login button and wait for auth to succeed.
+          await login.tap(find.byValueKey('login'));
+          await login.waitForAbsent(find.byType('Login'),
+              timeout: Duration(minutes: 2));
+          print('password entered');
+        }
+      }
+    }
+
+    print('Starting ermine shell');
+    await inspectSnapshot(kLoginInspectSelector,
+        predicate: (snapshot) => snapshot['ermineReady'] == true);
+    // We should land on the Ermine shell.
+    await login.waitUntilNoTransientCallbacks();
+    await login.waitFor(find.byType('ErmineApp'));
+
+    return connectToFlutterDriver('ermine', kErmineInspectSelector);
+  }
+
+  /// Performs a logout followed by login authentication flow.
+  ///
+  /// This is done in one flow to ensure ermine's flutter [_driver] is valid at
+  /// the end of the flow.
+  Future<void> logoutAndLogin() async {
+    // Ensure that ermine shell is running.
+    await login.waitFor(find.byType('ErmineApp'));
+    await inspectSnapshot(kErmineInspectSelector);
+    await driver.waitUntilNoTransientCallbacks();
+
+    // Send logout action
+    await driver.requestData('logout');
+    await driver.waitUntilNoTransientCallbacks();
+
+    // Tap 'Log out' button to confirm logout dialog, but don't wait for it to
+    // complete because ermine is killed.
+    // ignore: unawaited_futures
+    driver.tap(find.text('LOGOUT')).catchError((_) {});
+    // ignore: unawaited_futures
+    driver.close();
+
+    // If launchOOBE is true, wait for the login screen.
+    final snapshot = await inspectSnapshot(kLoginInspectSelector,
+        predicate: (snapshot) => snapshot['ready'] == true);
+    if (snapshot['launchOOBE'] == true) {
+      await login.waitUntilNoTransientCallbacks();
+      await login.waitFor(find.byType('Login'));
+    }
+
+    // Now log back in and set the new flutter driver connection to ermine.
+    _driver = await authenticate();
+  }
+
+  /// Enters text using [Input] to [textField] using [driver].
+  Future<void> enterText(
+    String text, {
+    required FlutterDriver driver,
+    required SerializableFinder textField,
+  }) async {
+    final input = Input(sl4f);
+    // Tap on the text field.
+    await driver.tap(textField);
+    await input.text(text);
+    // Wait for input to make to the text field.
+    await waitFor(() async {
+      return (await driver.getText(textField)) == text;
+    });
+  }
 }
 
 /// Holds Ermine shell state which is derived from inspect data.
@@ -529,6 +694,8 @@
   bool get appBarVisible => inspectData['appBarVisible'] == true;
   bool get sideBarVisible => inspectData['sideBarVisible'] == true;
   bool get overlaysVisible => inspectData['overlaysVisible'] == true;
+  String get lastAction => inspectData['lastAction'] ?? '';
+  bool get darkMode => inspectData['darkMode'] ?? true;
   ViewSnapshot? get activeView =>
       numViews > 0 ? views[inspectData['activeView'] ?? 0] : null;
 
diff --git a/tests/lib/simple_browser_driver.dart b/tests/lib/simple_browser_driver.dart
deleted file mode 100644
index f32a6d6..0000000
--- a/tests/lib/simple_browser_driver.dart
+++ /dev/null
@@ -1,98 +0,0 @@
-// Copyright 2021 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'dart:async';
-
-// ignore_for_file: import_of_legacy_library_into_null_safe
-
-import 'package:ermine_driver/ermine_driver.dart';
-import 'package:flutter_driver/flutter_driver.dart';
-import 'package:flutter_driver_sl4f/flutter_driver_sl4f.dart';
-import 'package:test/test.dart';
-
-class SimpleBrowserDriver {
-  final ErmineDriver _ermine;
-  final FlutterDriverConnector _connector;
-  FlutterDriver? _browser;
-
-  FlutterDriver get driver => _browser!;
-
-  SimpleBrowserDriver(this._ermine)
-      : _connector = FlutterDriverConnector(_ermine.sl4f);
-
-  /// Launches a simple browser and returns a [FlutterDriver] connected to it.
-  Future<void> launchSimpleBrowser() async {
-    expect(await _ermine.launch(simpleBrowserUrl), isTrue);
-    print('Launched a browser');
-
-    // Initializes the browser's flutter driver connector.
-    await _connector.initialize();
-    print('Initialized a flutter driver connector for the browser.');
-
-    // Checks if Simple Browser is running.
-    // TODO(fxb/66577): Get the last isolate once it's supported by
-    // [FlutterDriverConnector] in flutter_driver_sl4f.dart
-    final browserIsolate = await _connector.isolate('simple-browser');
-    // ignore: unnecessary_null_comparison
-    if (browserIsolate == null) {
-      fail('couldn\'t find simple browser.');
-    }
-    print('Checked that the browser is running.');
-
-    // Connects to the browser.
-    // TODO(fxb/66577): Get the driver of the last isolate once it's supported by
-    // [FlutterDriverConnector] in flutter_driver_sl4f.dart
-    _browser = await _connector.driverForIsolate('simple-browser');
-    // ignore: unnecessary_null_comparison
-    if (_browser == null) {
-      fail('unable to connect to simple browser.');
-    }
-    print('Connected the browser to a flutter driver.');
-  }
-
-  /// Launches a simple browser and sets up options for test convenience.
-  ///
-  /// Opens another new tab as soon as the browser is launched, unless you set
-  /// [openNewTab] to false. Contrarily, set [fullscreen] to true if you want
-  /// the browser to expand its size to full-screen upon its launch.
-  /// Also, you can set the text entry emulation of the browser's flutter driver
-  /// using [enableTextEntryEmulation], which has false by default.
-  Future<void> launchAndWaitForSimpleBrowser({
-    bool openNewTab = true,
-    bool enableTextEntryEmulation = false,
-  }) async {
-    await launchSimpleBrowser();
-
-    if (_browser != null) {
-      // Set the flutter driver's text entry emulation.
-      await _browser!.setTextEntryEmulation(enabled: enableTextEntryEmulation);
-      print('Text entry emulation is enabled for the browser.');
-
-      // Opens another tab other than the tab opened on browser's launch,
-      // if required.
-      if (openNewTab) {
-        final addTab = find.byValueKey('new_tab');
-        await _browser!.waitFor(addTab);
-
-        await _browser!.tap(addTab);
-        await _browser!
-            .waitFor(find.text('NEW TAB'), timeout: Duration(seconds: 10));
-        print('Opened a new tab');
-      } else {
-        await _browser!
-            .waitFor(find.text('     SEARCH'), timeout: Duration(seconds: 10));
-        print('The first tab is ready.');
-      }
-
-      await _browser!.waitUntilFirstFrameRasterized();
-      await _browser!.waitUntilNoTransientCallbacks();
-      print('No further transient callbacks.');
-    }
-  }
-
-  Future<void> tearDown() async {
-    await _browser?.close();
-    await _connector.tearDown();
-  }
-}