diff --git a/.eslintignore b/.eslintignore
index c6631e5f6873..1ec3c5e12a98 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -28,3 +28,5 @@ sticker-creator/**/*.js
 .eslintrc.js
 webpack.config.ts
 preload.bundle.*
+about.browser.bundle.*
+about.preload.bundle.*
diff --git a/.gitignore b/.gitignore
index 881b436355cd..1a0e01389ffe 100644
--- a/.gitignore
+++ b/.gitignore
@@ -26,6 +26,8 @@ libtextsecure/components.js
 stylesheets/*.css
 /storybook-static/
 preload.bundle.*
+about.browser.bundle.*
+about.preload.bundle.*
 ts/sql/mainWorker.bundle.js.LICENSE.txt
 
 # React / TypeScript
diff --git a/.prettierignore b/.prettierignore
index c4bdad39484e..d404a3229150 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -43,3 +43,5 @@ js/WebAudioRecorderMp3.js
 stylesheets/_intlTelInput.scss
 
 preload.bundle.*
+about.browser.bundle.*
+about.preload.bundle.*
diff --git a/about.html b/about.html
index 81185ccefdf5..e5e5e921d3dd 100644
--- a/about.html
+++ b/about.html
@@ -30,5 +30,9 @@
   <body>
     <div id="app"></div>
     <script type="application/javascript" src="ts/windows/init.js"></script>
+    <script
+      type="application/javascript"
+      src="about.browser.bundle.js"
+    ></script>
   </body>
 </html>
diff --git a/app/main.ts b/app/main.ts
index 595a5ef81ee2..fdd964905a74 100644
--- a/app/main.ts
+++ b/app/main.ts
@@ -1253,7 +1253,7 @@ async function showAbout() {
       nodeIntegrationInWorker: false,
       sandbox: false,
       contextIsolation: true,
-      preload: join(__dirname, '../ts/windows/about/preload.js'),
+      preload: join(__dirname, '../about.preload.bundle.js'),
       nativeWindowOpen: true,
     },
   };
@@ -2357,6 +2357,11 @@ ipc.on('locale-data', event => {
   event.returnValue = getResolvedMessagesLocale().messages;
 });
 
+ipc.on('getHasCustomTitleBar', event => {
+  // eslint-disable-next-line no-param-reassign
+  event.returnValue = OS.hasCustomTitleBar();
+});
+
 ipc.on('user-config-key', event => {
   // eslint-disable-next-line no-param-reassign
   event.returnValue = userConfig.get('key');
diff --git a/package.json b/package.json
index 92046b5b86ca..5a18d98046fd 100644
--- a/package.json
+++ b/package.json
@@ -462,6 +462,8 @@
       "app/*",
       "preload.bundle.js",
       "preload_utils.js",
+      "about.preload.bundle.js",
+      "about.browser.bundle.js",
       "main.js",
       "images/**",
       "fonts/**",
diff --git a/scripts/esbuild.js b/scripts/esbuild.js
index d506969b6156..064df944aa6d 100644
--- a/scripts/esbuild.js
+++ b/scripts/esbuild.js
@@ -98,3 +98,18 @@ main().catch(error => {
   console.error(error.stack);
   process.exit(1);
 });
+
+// About bundle
+esbuild.build({
+  ...bundleDefaults,
+  mainFields: ['browser', 'main'],
+  entryPoints: [path.join(ROOT_DIR, 'ts', 'windows', 'about', 'app.tsx')],
+  outfile: path.join(ROOT_DIR, 'about.browser.bundle.js'),
+});
+
+esbuild.build({
+  ...bundleDefaults,
+  mainFields: ['browser', 'main'],
+  entryPoints: [path.join(ROOT_DIR, 'ts', 'windows', 'about', 'preload.ts')],
+  outfile: path.join(ROOT_DIR, 'about.preload.bundle.js'),
+});
diff --git a/ts/background.ts b/ts/background.ts
index 4bc5714c56c6..9f6fb95e07da 100644
--- a/ts/background.ts
+++ b/ts/background.ts
@@ -148,7 +148,7 @@ import { deleteAllLogs } from './util/deleteAllLogs';
 import { ToastCaptchaFailed } from './components/ToastCaptchaFailed';
 import { ToastCaptchaSolved } from './components/ToastCaptchaSolved';
 import { showToast } from './util/showToast';
-import { startInteractionMode } from './windows/startInteractionMode';
+import { startInteractionMode } from './services/InteractionMode';
 import type { MainWindowStatsType } from './windows/context';
 import { ReactionSource } from './reactions/ReactionSource';
 import { singleProtoJobQueue } from './jobs/singleProtoJobQueue';
diff --git a/ts/components/About.tsx b/ts/components/About.tsx
index 9b297fa6b7f0..4346dc06bc1d 100644
--- a/ts/components/About.tsx
+++ b/ts/components/About.tsx
@@ -3,28 +3,28 @@
 
 import React from 'react';
 
+import type { ExecuteMenuRoleType } from './TitleBarContainer';
 import type { LocalizerType } from '../types/Util';
+import { TitleBarContainer } from './TitleBarContainer';
 import { useEscapeHandling } from '../hooks/useEscapeHandling';
 import { useTheme } from '../hooks/useTheme';
-import { TitleBarContainer } from './TitleBarContainer';
-import type { ExecuteMenuRoleType } from './TitleBarContainer';
 
 export type PropsType = {
   closeAbout: () => unknown;
   environment: string;
+  executeMenuRole: ExecuteMenuRoleType;
+  hasCustomTitleBar: boolean;
   i18n: LocalizerType;
   version: string;
-  hasCustomTitleBar: boolean;
-  executeMenuRole: ExecuteMenuRoleType;
 };
 
 export function About({
   closeAbout,
-  i18n,
   environment,
-  version,
-  hasCustomTitleBar,
   executeMenuRole,
+  hasCustomTitleBar,
+  i18n,
+  version,
 }: PropsType): JSX.Element {
   useEscapeHandling(closeAbout);
 
diff --git a/ts/components/Tooltip.tsx b/ts/components/Tooltip.tsx
index 1783e46ae337..4f16732eae80 100644
--- a/ts/components/Tooltip.tsx
+++ b/ts/components/Tooltip.tsx
@@ -10,6 +10,7 @@ import type { Theme } from '../util/theme';
 import { themeClassName } from '../util/theme';
 import { refMerger } from '../util/refMerger';
 import { offsetDistanceModifier } from '../util/popperUtil';
+import { getInteractionMode } from '../services/InteractionMode';
 
 type EventWrapperPropsType = {
   children: React.ReactNode;
@@ -35,7 +36,7 @@ const TooltipEventWrapper = React.forwardRef<
   }, [onHoverChanged]);
 
   const onFocus = React.useCallback(() => {
-    if (window.getInteractionMode() === 'keyboard') {
+    if (getInteractionMode() === 'keyboard') {
       on();
     }
   }, [on]);
diff --git a/ts/components/conversation/InlineNotificationWrapper.tsx b/ts/components/conversation/InlineNotificationWrapper.tsx
index b63f8a282196..fa82f09c3b07 100644
--- a/ts/components/conversation/InlineNotificationWrapper.tsx
+++ b/ts/components/conversation/InlineNotificationWrapper.tsx
@@ -2,6 +2,7 @@
 // SPDX-License-Identifier: AGPL-3.0-only
 
 import React from 'react';
+import { getInteractionMode } from '../../services/InteractionMode';
 
 export type PropsType = {
   id: string;
@@ -22,7 +23,7 @@ export class InlineNotificationWrapper extends React.Component<PropsType> {
   };
 
   public handleFocus = (): void => {
-    if (window.getInteractionMode() === 'keyboard') {
+    if (getInteractionMode() === 'keyboard') {
       this.setSelected();
     }
   };
diff --git a/ts/context/activeWindowService.ts b/ts/context/activeWindowService.ts
new file mode 100644
index 000000000000..0e5dc866733c
--- /dev/null
+++ b/ts/context/activeWindowService.ts
@@ -0,0 +1,12 @@
+// Copyright 2023 Signal Messenger, LLC
+// SPDX-License-Identifier: AGPL-3.0-only
+
+import { ipcRenderer } from 'electron';
+import { getActiveWindowService } from '../services/ActiveWindowService';
+
+const activeWindowService = getActiveWindowService(
+  window.document,
+  ipcRenderer
+);
+
+export { activeWindowService };
diff --git a/ts/context/config.ts b/ts/context/config.ts
new file mode 100644
index 000000000000..c67f20adf4c1
--- /dev/null
+++ b/ts/context/config.ts
@@ -0,0 +1,12 @@
+// Copyright 2023 Signal Messenger, LLC
+// SPDX-License-Identifier: AGPL-3.0-only
+
+import type { RendererConfigType } from '../types/RendererConfig';
+import { strictAssert } from '../util/assert';
+
+const params = new URLSearchParams(document.location.search);
+const configParam = params.get('config');
+strictAssert(typeof configParam === 'string', 'config is not a string');
+const config: RendererConfigType = JSON.parse(configParam);
+
+export { config };
diff --git a/ts/context/environment.ts b/ts/context/environment.ts
new file mode 100644
index 000000000000..7171edebcaf4
--- /dev/null
+++ b/ts/context/environment.ts
@@ -0,0 +1,15 @@
+// Copyright 2023 Signal Messenger, LLC
+// SPDX-License-Identifier: AGPL-3.0-only
+
+import { config } from './config';
+import {
+  getEnvironment,
+  parseEnvironment,
+  setEnvironment,
+} from '../environment';
+
+setEnvironment(parseEnvironment(config.environment));
+
+const environment = getEnvironment();
+
+export { environment };
diff --git a/ts/context/i18n.ts b/ts/context/i18n.ts
new file mode 100644
index 000000000000..edf36636c287
--- /dev/null
+++ b/ts/context/i18n.ts
@@ -0,0 +1,22 @@
+// Copyright 2023 Signal Messenger, LLC
+// SPDX-License-Identifier: AGPL-3.0-only
+
+import { ipcRenderer } from 'electron';
+import { config } from './config';
+import { setupI18n } from '../util/setupI18n';
+import { strictAssert } from '../util/assert';
+
+const { resolvedTranslationsLocale } = config;
+strictAssert(
+  resolvedTranslationsLocale,
+  'locale could not be parsed from config'
+);
+strictAssert(
+  typeof resolvedTranslationsLocale === 'string',
+  'locale is not a string'
+);
+
+const localeMessages = ipcRenderer.sendSync('locale-data');
+const i18n = setupI18n(resolvedTranslationsLocale, localeMessages);
+
+export { i18n };
diff --git a/ts/windows/waitForSettingsChange.ts b/ts/context/waitForSettingsChange.ts
similarity index 100%
rename from ts/windows/waitForSettingsChange.ts
rename to ts/context/waitForSettingsChange.ts
diff --git a/ts/services/ActiveWindowService.ts b/ts/services/ActiveWindowService.ts
index a63420e3318b..402502ff2e15 100644
--- a/ts/services/ActiveWindowService.ts
+++ b/ts/services/ActiveWindowService.ts
@@ -16,7 +16,7 @@ const ACTIVE_EVENTS = [
   'wheel',
 ];
 
-export class ActiveWindowService {
+class ActiveWindowService {
   // This starting value might be wrong but we should get an update from the main process
   //  soon. We'd rather report that the window is inactive so we can show notifications.
   private isInitialized = false;
@@ -113,3 +113,37 @@ export class ActiveWindowService {
     }
   }
 }
+
+export type ActiveWindowServiceType = {
+  isActive(): boolean;
+  registerForActive(callback: () => void): void;
+  unregisterForActive(callback: () => void): void;
+  registerForChange(callback: (isActive: boolean) => void): void;
+  unregisterForChange(callback: (isActive: boolean) => void): void;
+};
+
+export function getActiveWindowService(
+  document: EventTarget,
+  ipc: NodeJS.EventEmitter
+): ActiveWindowServiceType {
+  const activeWindowService = new ActiveWindowService();
+  activeWindowService.initialize(document, ipc);
+
+  return {
+    isActive(): boolean {
+      return activeWindowService.isActive();
+    },
+    registerForActive(callback: () => void): void {
+      return activeWindowService.registerForActive(callback);
+    },
+    unregisterForActive(callback: () => void): void {
+      return activeWindowService.unregisterForActive(callback);
+    },
+    registerForChange(callback: (isActive: boolean) => void): void {
+      return activeWindowService.registerForChange(callback);
+    },
+    unregisterForChange(callback: (isActive: boolean) => void): void {
+      return activeWindowService.unregisterForChange(callback);
+    },
+  };
+}
diff --git a/ts/windows/startInteractionMode.ts b/ts/services/InteractionMode.ts
similarity index 89%
rename from ts/windows/startInteractionMode.ts
rename to ts/services/InteractionMode.ts
index 68f266f2a96a..d3b015d72539 100644
--- a/ts/windows/startInteractionMode.ts
+++ b/ts/services/InteractionMode.ts
@@ -1,8 +1,10 @@
 // Copyright 2021 Signal Messenger, LLC
 // SPDX-License-Identifier: AGPL-3.0-only
 
+type InteractionModeType = 'mouse' | 'keyboard';
+
 let initialized = false;
-let interactionMode: 'mouse' | 'keyboard' = 'mouse';
+let interactionMode: InteractionModeType = 'mouse';
 
 export function startInteractionMode(): void {
   if (initialized) {
@@ -66,6 +68,8 @@ export function startInteractionMode(): void {
   );
   document.addEventListener('wheel', window.enterMouseMode, true);
   document.addEventListener('mousedown', window.enterMouseMode, true);
-
-  window.getInteractionMode = () => interactionMode;
+}
+
+export function getInteractionMode(): InteractionModeType {
+  return interactionMode;
 }
diff --git a/ts/state/getInitialState.ts b/ts/state/getInitialState.ts
index 4d0ce96139f6..6c74879e4edc 100644
--- a/ts/state/getInitialState.ts
+++ b/ts/state/getInitialState.ts
@@ -36,6 +36,7 @@ import { UUIDKind } from '../types/UUID';
 import { getEmojiReducerState as emojis } from '../util/loadRecentEmojis';
 import { getInitialState as stickers } from '../types/Stickers';
 import { getThemeType } from '../util/getThemeType';
+import { getInteractionMode } from '../services/InteractionMode';
 
 export function getInitialState({
   badges,
@@ -137,7 +138,7 @@ export function getInitialState({
       ...user(),
       attachmentsPath: window.BasePaths.attachments,
       i18n: window.i18n,
-      interactionMode: window.getInteractionMode(),
+      interactionMode: getInteractionMode(),
       isMainWindowFullScreen: mainWindowStats.isFullScreen,
       isMainWindowMaximized: mainWindowStats.isMaximized,
       localeMessages: window.SignalContext.localeMessages,
diff --git a/ts/test-electron/services/ActiveWindowService_test.ts b/ts/test-electron/services/ActiveWindowService_test.ts
index d73b312ca73a..8d89e15e21b7 100644
--- a/ts/test-electron/services/ActiveWindowService_test.ts
+++ b/ts/test-electron/services/ActiveWindowService_test.ts
@@ -5,7 +5,7 @@ import { assert } from 'chai';
 import * as sinon from 'sinon';
 import { EventEmitter } from 'events';
 
-import { ActiveWindowService } from '../../services/ActiveWindowService';
+import { getActiveWindowService } from '../../services/ActiveWindowService';
 
 describe('ActiveWindowService', () => {
   const fakeIpcEvent = {};
@@ -23,16 +23,17 @@ describe('ActiveWindowService', () => {
   }
 
   it('is inactive at the start', () => {
-    const service = new ActiveWindowService();
-    service.initialize(createFakeDocument(), new EventEmitter());
+    const service = getActiveWindowService(
+      createFakeDocument(),
+      new EventEmitter()
+    );
 
     assert.isFalse(service.isActive());
   });
 
   it('becomes active after focusing', () => {
     const fakeIpc = new EventEmitter();
-    const service = new ActiveWindowService();
-    service.initialize(createFakeDocument(), fakeIpc);
+    const service = getActiveWindowService(createFakeDocument(), fakeIpc);
 
     fakeIpc.emit('set-window-focus', fakeIpcEvent, true);
 
@@ -41,8 +42,7 @@ describe('ActiveWindowService', () => {
 
   it('becomes inactive after 15 seconds without interaction', function test() {
     const fakeIpc = new EventEmitter();
-    const service = new ActiveWindowService();
-    service.initialize(createFakeDocument(), fakeIpc);
+    const service = getActiveWindowService(createFakeDocument(), fakeIpc);
 
     fakeIpc.emit('set-window-focus', fakeIpcEvent, true);
 
@@ -61,8 +61,7 @@ describe('ActiveWindowService', () => {
       it(`is inactive even in the face of ${eventName} events if unfocused`, function test() {
         const fakeDocument = createFakeDocument();
         const fakeIpc = new EventEmitter();
-        const service = new ActiveWindowService();
-        service.initialize(fakeDocument, fakeIpc);
+        const service = getActiveWindowService(fakeDocument, fakeIpc);
 
         fakeIpc.emit('set-window-focus', fakeIpcEvent, false);
 
@@ -73,8 +72,7 @@ describe('ActiveWindowService', () => {
       it(`stays active if focused and receiving ${eventName} events`, function test() {
         const fakeDocument = createFakeDocument();
         const fakeIpc = new EventEmitter();
-        const service = new ActiveWindowService();
-        service.initialize(fakeDocument, fakeIpc);
+        const service = getActiveWindowService(fakeDocument, fakeIpc);
 
         fakeIpc.emit('set-window-focus', fakeIpcEvent, true);
 
@@ -94,8 +92,7 @@ describe('ActiveWindowService', () => {
 
   it('calls callbacks when going from unfocused to focused', () => {
     const fakeIpc = new EventEmitter();
-    const service = new ActiveWindowService();
-    service.initialize(createFakeDocument(), fakeIpc);
+    const service = getActiveWindowService(createFakeDocument(), fakeIpc);
 
     const callback = sinon.stub();
     service.registerForActive(callback);
@@ -108,8 +105,7 @@ describe('ActiveWindowService', () => {
   it('calls callbacks when receiving a click event after being focused', function test() {
     const fakeDocument = createFakeDocument();
     const fakeIpc = new EventEmitter();
-    const service = new ActiveWindowService();
-    service.initialize(fakeDocument, fakeIpc);
+    const service = getActiveWindowService(fakeDocument, fakeIpc);
 
     fakeIpc.emit('set-window-focus', fakeIpcEvent, true);
 
@@ -125,8 +121,7 @@ describe('ActiveWindowService', () => {
 
   it('only calls callbacks every 5 seconds; it is throttled', function test() {
     const fakeIpc = new EventEmitter();
-    const service = new ActiveWindowService();
-    service.initialize(createFakeDocument(), fakeIpc);
+    const service = getActiveWindowService(createFakeDocument(), fakeIpc);
 
     const callback = sinon.stub();
     service.registerForActive(callback);
@@ -150,8 +145,7 @@ describe('ActiveWindowService', () => {
   it('can remove callbacks', () => {
     const fakeDocument = createFakeDocument();
     const fakeIpc = new EventEmitter();
-    const service = new ActiveWindowService();
-    service.initialize(fakeDocument, fakeIpc);
+    const service = getActiveWindowService(fakeDocument, fakeIpc);
 
     const callback = sinon.stub();
     service.registerForActive(callback);
diff --git a/ts/util/lint/linter.ts b/ts/util/lint/linter.ts
index b78c3dbe5a86..d4ed74b7c218 100644
--- a/ts/util/lint/linter.ts
+++ b/ts/util/lint/linter.ts
@@ -24,6 +24,8 @@ const excludedFilesRegexp = RegExp(
   [
     '^release/',
     '^preload.bundle.js(LICENSE.txt|map)?',
+    '^about.browser.bundle.js(LICENSE.txt|map)?',
+    '^about.preload.bundle.js(LICENSE.txt|map)?',
     '^storybook-static/',
 
     // Non-distributed files
diff --git a/ts/window.d.ts b/ts/window.d.ts
index 7cfabbe4d95d..ec0bf80d6ee9 100644
--- a/ts/window.d.ts
+++ b/ts/window.d.ts
@@ -3,6 +3,7 @@
 
 // Captures the globals put in place by preload.js, background.js and others
 
+import type { MenuItemConstructorOptions } from 'electron';
 import type { Store } from 'redux';
 import type * as Backbone from 'backbone';
 import type PQueue from 'p-queue/dist';
@@ -101,7 +102,16 @@ export type FeatureFlagType = {
   GV2_MIGRATION_DISABLE_INVITE: boolean;
 };
 
+type AboutWindowType = {
+  environmentText: string;
+  executeMenuRole: (role: MenuItemConstructorOptions['role']) => Promise<void>;
+  hasCustomTitleBar: boolean;
+  i18n: LocalizerType;
+  version: string;
+};
+
 export type SignalCoreType = {
+  AboutWindow?: AboutWindowType;
   Crypto: typeof Crypto;
   Curve: typeof Curve;
   Data: typeof Data;
diff --git a/ts/windows/about/app.tsx b/ts/windows/about/app.tsx
new file mode 100644
index 000000000000..5964e7d2c4e4
--- /dev/null
+++ b/ts/windows/about/app.tsx
@@ -0,0 +1,24 @@
+// Copyright 2023 Signal Messenger, LLC
+// SPDX-License-Identifier: AGPL-3.0-only
+
+import React from 'react';
+import ReactDOM from 'react-dom';
+
+import { About } from '../../components/About';
+import { strictAssert } from '../../util/assert';
+
+const { AboutWindow } = window.Signal;
+
+strictAssert(AboutWindow, 'window values not provided');
+
+ReactDOM.render(
+  <About
+    closeAbout={() => AboutWindow.executeMenuRole('close')}
+    environment={AboutWindow.environmentText}
+    executeMenuRole={AboutWindow.executeMenuRole}
+    hasCustomTitleBar={AboutWindow.hasCustomTitleBar}
+    i18n={AboutWindow.i18n}
+    version={AboutWindow.version}
+  />,
+  document.getElementById('app')
+);
diff --git a/ts/windows/about/preload.ts b/ts/windows/about/preload.ts
index 9ebf2f2816aa..ee31c2150ad3 100644
--- a/ts/windows/about/preload.ts
+++ b/ts/windows/about/preload.ts
@@ -1,42 +1,63 @@
 // Copyright 2018 Signal Messenger, LLC
 // SPDX-License-Identifier: AGPL-3.0-only
 
-import React from 'react';
-import ReactDOM from 'react-dom';
-import { contextBridge } from 'electron';
+import type { MenuItemConstructorOptions } from 'electron';
+import { contextBridge, ipcRenderer } from 'electron';
+import { activeWindowService } from '../../context/activeWindowService';
+import { config } from '../../context/config';
+import { createNativeThemeListener } from '../../context/createNativeThemeListener';
+import { createSetting } from '../../util/preload';
+import { environment } from '../../context/environment';
+import { getClassName } from '../../OS';
+import { i18n } from '../../context/i18n';
+import { waitForSettingsChange } from '../../context/waitForSettingsChange';
 
-import { SignalContext } from '../context';
-import { About } from '../../components/About';
+async function executeMenuRole(
+  role: MenuItemConstructorOptions['role']
+): Promise<void> {
+  await ipcRenderer.invoke('executeMenuRole', role);
+}
 
-contextBridge.exposeInMainWorld('SignalContext', {
-  ...SignalContext,
-  renderWindow: () => {
-    const environmentText: Array<string> = [SignalContext.getEnvironment()];
+const environments: Array<string> = [environment];
 
-    const appInstance = SignalContext.getAppInstance();
-    if (appInstance) {
-      environmentText.push(appInstance);
-    }
+if (config.appInstance) {
+  environments.push(String(config.appInstance));
+}
 
-    let platform = '';
-    if (process.platform === 'darwin') {
-      if (process.arch === 'arm64') {
-        platform = ` (${SignalContext.i18n('appleSilicon')})`;
-      } else {
-        platform = ' (Intel)';
-      }
-    }
+let platform = '';
+if (process.platform === 'darwin') {
+  if (process.arch === 'arm64') {
+    platform = ` (${i18n('appleSilicon')})`;
+  } else {
+    platform = ' (Intel)';
+  }
+}
 
-    ReactDOM.render(
-      React.createElement(About, {
-        closeAbout: () => SignalContext.executeMenuRole('close'),
-        environment: `${environmentText.join(' - ')}${platform}`,
-        i18n: SignalContext.i18n,
-        version: SignalContext.getVersion(),
-        hasCustomTitleBar: SignalContext.OS.hasCustomTitleBar(),
-        executeMenuRole: SignalContext.executeMenuRole,
-      }),
-      document.getElementById('app')
-    );
+const environmentText = `${environments.join(' - ')}${platform}`;
+const hasCustomTitleBar = ipcRenderer.sendSync('getHasCustomTitleBar');
+
+const Signal = {
+  AboutWindow: {
+    environmentText,
+    executeMenuRole,
+    hasCustomTitleBar,
+    i18n,
+    version: String(config.version),
   },
-});
+};
+contextBridge.exposeInMainWorld('Signal', Signal);
+
+// TODO DESKTOP-5054
+const SignalContext = {
+  activeWindowService,
+  OS: {
+    getClassName,
+    hasCustomTitleBar: () => hasCustomTitleBar,
+  },
+  Settings: {
+    themeSetting: createSetting('themeSetting', { setter: false }),
+    waitForChange: waitForSettingsChange,
+  },
+  nativeThemeListener: createNativeThemeListener(ipcRenderer, window),
+};
+contextBridge.exposeInMainWorld('SignalContext', SignalContext);
diff --git a/ts/windows/context.ts b/ts/windows/context.ts
index 9fb404c41665..82587506f787 100644
--- a/ts/windows/context.ts
+++ b/ts/windows/context.ts
@@ -12,13 +12,15 @@ import type { LocaleMessagesType } from '../types/I18N';
 import type { NativeThemeType } from '../context/createNativeThemeListener';
 import type { SettingType } from '../util/preload';
 import type { RendererConfigType } from '../types/RendererConfig';
-import { ActiveWindowService } from '../services/ActiveWindowService';
 
 import { Bytes } from '../context/Bytes';
 import { Crypto } from '../context/Crypto';
 import { Timers } from '../context/Timers';
 
-import { setupI18n } from '../util/setupI18n';
+import type { ActiveWindowServiceType } from '../services/ActiveWindowService';
+import { config } from '../context/config';
+import { i18n } from '../context/i18n';
+import { activeWindowService } from '../context/activeWindowService';
 import {
   getEnvironment,
   parseEnvironment,
@@ -27,7 +29,7 @@ import {
 import { strictAssert } from '../util/assert';
 import { createSetting } from '../util/preload';
 import { initialize as initializeLogging } from '../logging/set_up_renderer_logging';
-import { waitForSettingsChange } from './waitForSettingsChange';
+import { waitForSettingsChange } from '../context/waitForSettingsChange';
 import { createNativeThemeListener } from '../context/createNativeThemeListener';
 import {
   isWindows,
@@ -37,24 +39,6 @@ import {
   getClassName,
 } from '../OS';
 
-const activeWindowService = new ActiveWindowService();
-activeWindowService.initialize(window.document, ipcRenderer);
-
-const params = new URLSearchParams(document.location.search);
-const configParam = params.get('config');
-strictAssert(typeof configParam === 'string', 'config is not a string');
-const config: RendererConfigType = JSON.parse(configParam);
-
-const { resolvedTranslationsLocale } = config;
-strictAssert(
-  resolvedTranslationsLocale,
-  'locale could not be parsed from config'
-);
-strictAssert(
-  typeof resolvedTranslationsLocale === 'string',
-  'locale is not a string'
-);
-
 const localeMessages = ipcRenderer.sendSync('locale-data');
 setEnvironment(parseEnvironment(config.environment));
 
@@ -74,7 +58,7 @@ export type SignalContextType = {
   nativeThemeListener: NativeThemeType;
   setIsCallActive: (isCallActive: boolean) => unknown;
 
-  activeWindowService: typeof activeWindowService;
+  activeWindowService: ActiveWindowServiceType;
   Settings: {
     themeSetting: SettingType<IPCEventsValuesType['themeSetting']>;
     waitForChange: () => Promise<void>;
@@ -128,7 +112,7 @@ export const SignalContext: SignalContextType = {
   getPath: (name: 'userData' | 'home'): string => {
     return String(config[`${name}Path`]);
   },
-  i18n: setupI18n(resolvedTranslationsLocale, localeMessages),
+  i18n,
   localeMessages,
   log: window.SignalContext.log,
   nativeThemeListener: createNativeThemeListener(ipcRenderer, window),
diff --git a/ts/windows/init.ts b/ts/windows/init.ts
index f2109b85d36d..413980ab2423 100644
--- a/ts/windows/init.ts
+++ b/ts/windows/init.ts
@@ -8,6 +8,6 @@ if (window.SignalContext.OS.hasCustomTitleBar()) {
 
 if (window.SignalContext.renderWindow) {
   window.SignalContext.renderWindow();
-} else {
+} else if (window.SignalContext.log) {
   window.SignalContext.log.error('renderWindow is undefined!');
 }
diff --git a/ts/windows/settings/preload.ts b/ts/windows/settings/preload.ts
index 235d846aa393..90a4b2b878db 100644
--- a/ts/windows/settings/preload.ts
+++ b/ts/windows/settings/preload.ts
@@ -16,7 +16,7 @@ import {
 import { awaitObject } from '../../util/awaitObject';
 import { DurationInSeconds } from '../../util/durations';
 import { createSetting, createCallback } from '../../util/preload';
-import { startInteractionMode } from '../startInteractionMode';
+import { startInteractionMode } from '../../services/InteractionMode';
 
 function doneRendering() {
   ipcRenderer.send('settings-done-rendering');