diff --git a/.github/workflows/build-helper.yml b/.github/workflows/build-helper.yml
index 3a4a53f23..60d298fd9 100644
--- a/.github/workflows/build-helper.yml
+++ b/.github/workflows/build-helper.yml
@@ -3,11 +3,12 @@
 # For more information on the Windows Code Signing, see https://docs.digicert.com/en/digicert-keylocker/ci-cd-integrations/plugins/github-custom-action-for-keypair-signing.html and https://docs.digicert.com/en/digicert-keylocker/signing-tools/sign-authenticode-with-electron-builder-using-ksp-integration.html
 
 name: Build Helper
-run-name: Build ${{ github.ref_name }}
+run-name: Build ${{ github.ref_name }}${{ github.event_name == 'workflow_dispatch' && ' - Manual'}}
 on:
     push:
         tags:
             - "v[0-9]+.[0-9]+.[0-9]+*"
+    workflow_dispatch:
 env:
     GO_VERSION: "1.22"
     NODE_VERSION: "20"
@@ -39,11 +40,8 @@ jobs:
               run: |
                   sudo apt-get update
                   sudo apt-get install --no-install-recommends -y libarchive-tools libopenjp2-tools rpm squashfs-tools
-
-            # We use Zig instead of glibc for cgo compilation as it is more-easily statically linked
-            - name: Setup Zig (Linux only)
-              if: matrix.platform == 'linux'
-              run: sudo snap install zig --classic --beta
+                  sudo snap install snapcraft --classic
+                  sudo snap install zig --classic --beta # We use Zig instead of glibc for cgo compilation as it is more-easily statically linked
 
             # The pre-installed version of the AWS CLI has a segfault problem so we'll install it via Homebrew instead.
             - name: Upgrade AWS CLI (Mac only)
@@ -84,12 +82,12 @@ jobs:
 
             # Windows Code Signing Setup
             - name: Set up certificate (Windows only)
-              if: matrix.platform == 'windows'
+              if: matrix.platform == 'windows' && github.event_name != 'workflow_dispatch'
               run: |
                   echo "${{ secrets.SM_CLIENT_CERT_FILE_B64 }}" | base64 --decode > /d/Certificate_pkcs12.p12
               shell: bash
             - name: Set signing variables (Windows only)
-              if: matrix.platform == 'windows'
+              if: matrix.platform == 'windows' && github.event_name != 'workflow_dispatch'
               id: variables
               run: |
                   echo "SM_HOST=${{ secrets.SM_HOST }}" >> "$GITHUB_ENV"
@@ -103,7 +101,7 @@ jobs:
                   echo "C:\Program Files\DigiCert\DigiCert Keylocker Tools" >> $GITHUB_PATH
               shell: bash
             - name: Setup Keylocker KSP (Windows only)
-              if: matrix.platform == 'windows'
+              if: matrix.platform == 'windows' && github.event_name != 'workflow_dispatch'
               run: |
                   curl -X GET  https://one.digicert.com/signingmanager/api-ui/v1/releases/Keylockertools-windows-x64.msi/download -H "x-api-key:%SM_API_KEY%" -o Keylockertools-windows-x64.msi
                   msiexec /i Keylockertools-windows-x64.msi /quiet /qn
@@ -121,8 +119,15 @@ jobs:
                   path: ${{env.STATIC_DOCSITE_PATH}}
 
             # Build and upload packages
-            - name: Build (not Windows)
-              if: matrix.platform != 'windows'
+            - name: Build (Linux)
+              if: matrix.platform == 'linux'
+              run: task package
+              env:
+                  USE_SYSTEM_FPM: true # Ensure that the installed version of FPM is used rather than the bundled one.
+                  STATIC_DOCSITE_PATH: ${{env.STATIC_DOCSITE_PATH}}
+                  SNAPCRAFT_BUILD_ENVIRONMENT: host
+            - name: Build (Darwin)
+              if: matrix.platform == 'darwin'
               run: task package
               env:
                   USE_SYSTEM_FPM: true # Ensure that the installed version of FPM is used rather than the bundled one.
@@ -132,7 +137,7 @@ jobs:
                   APPLE_APP_SPECIFIC_PASSWORD: ${{ matrix.platform == 'darwin' && secrets.PROD_MACOS_NOTARIZATION_PWD_2 }}
                   APPLE_TEAM_ID: ${{ matrix.platform == 'darwin' && secrets.PROD_MACOS_NOTARIZATION_TEAM_ID_2 }}
                   STATIC_DOCSITE_PATH: ${{env.STATIC_DOCSITE_PATH}}
-            - name: Build (Windows only)
+            - name: Build (Windows)
               if: matrix.platform == 'windows'
               run: task package
               env:
@@ -141,14 +146,16 @@ jobs:
                   CSC_KEY_PASSWORD: ${{ secrets.SM_CLIENT_CERT_PASSWORD }}
                   STATIC_DOCSITE_PATH: ${{env.STATIC_DOCSITE_PATH}}
               shell: powershell # electron-builder's Windows code signing package has some compatibility issues with pwsh, so we need to use Windows Powershell
+
             - name: Upload to S3 staging
+              if: github.event_name != 'workflow_dispatch'
               run: task artifacts:upload
               env:
                   AWS_ACCESS_KEY_ID: "${{ secrets.ARTIFACTS_KEY_ID }}"
                   AWS_SECRET_ACCESS_KEY: "${{ secrets.ARTIFACTS_KEY_SECRET }}"
                   AWS_DEFAULT_REGION: us-west-2
-
             - name: Create draft release
+              if: github.event_name != 'workflow_dispatch'
               uses: softprops/action-gh-release@v2
               with:
                   prerelease: ${{ contains(github.ref_name, '-beta') }}
@@ -166,3 +173,9 @@ jobs:
                       make/*.snap
                       make/*.flatpak
                       make/*.AppImage
+            - name: Upload build artifacts to workflow (manual runs only)
+              if: github.event_name == 'workflow_dispatch'
+              uses: actions/upload-artifact@v4
+              with:
+                  name: ${{matrix.runner}}
+                  path: make
diff --git a/electron-builder.config.cjs b/electron-builder.config.cjs
index 8768fcd3d..4415b26fb 100644
--- a/electron-builder.config.cjs
+++ b/electron-builder.config.cjs
@@ -58,7 +58,7 @@ const config = {
         artifactName: "${name}-${platform}-${arch}-${version}.${ext}",
         category: "TerminalEmulator",
         executableName: pkg.name,
-        target: ["zip", "deb", "rpm", "AppImage", "pacman"],
+        target: ["zip", "deb", "rpm", "snap", "AppImage", "pacman"],
         synopsis: pkg.description,
         description: null,
         desktop: {
@@ -84,6 +84,11 @@ const config = {
     appImage: {
         license: "LICENSE",
     },
+    snap: {
+        base: "core22",
+        confinement: "classic",
+        allowNativeWayland: true,
+    },
     publish: {
         provider: "generic",
         url: "https://dl.waveterm.dev/releases-w2",
diff --git a/frontend/app/modals/tos.tsx b/frontend/app/modals/tos.tsx
index fb572bdb5..da6e2afa2 100644
--- a/frontend/app/modals/tos.tsx
+++ b/frontend/app/modals/tos.tsx
@@ -95,7 +95,7 @@ const ModalPage1 = () => {
                         <div className="content-section-title">Telemetry</div>
                         <div className="content-section-text">
                             We collect minimal anonymous{" "}
-                            <a target="_blank" href="https://docs.waveterm.dev/reference/telemetry" rel={"noopener"}>
+                            <a target="_blank" href="https://docs.waveterm.dev/telemetry" rel={"noopener"}>
                                 telemetry data
                             </a>{" "}
                             to help us understand how people are using Wave (
diff --git a/package.json b/package.json
index 8c52ad3db..0a87ffb9c 100644
--- a/package.json
+++ b/package.json
@@ -55,8 +55,8 @@
         "@types/ws": "^8",
         "@vitejs/plugin-react-swc": "^3.7.1",
         "@vitest/coverage-istanbul": "^2.1.3",
-        "electron": "^32.2.0",
-        "electron-builder": "^25.1.7",
+        "electron": "^33.0.1",
+        "electron-builder": "^25.1.8",
         "electron-vite": "^2.3.0",
         "eslint": "^9.12.0",
         "eslint-config-prettier": "^9.1.0",
diff --git a/pkg/blockcontroller/blockcontroller.go b/pkg/blockcontroller/blockcontroller.go
index e4648a5bd..6b187a342 100644
--- a/pkg/blockcontroller/blockcontroller.go
+++ b/pkg/blockcontroller/blockcontroller.go
@@ -380,7 +380,10 @@ func (bc *BlockController) DoRunShellCommand(rc *RunShellOpts, blockMeta waveobj
 	go func() {
 		// handles outputCh -> shellInputCh
 		for msg := range wshProxy.ToRemoteCh {
-			encodedMsg := wshutil.EncodeWaveOSCBytes(wshutil.WaveServerOSC, msg)
+			encodedMsg, err := wshutil.EncodeWaveOSCBytes(wshutil.WaveServerOSC, msg)
+			if err != nil {
+				log.Printf("error encoding OSC message: %v\n", err)
+			}
 			shellInputCh <- &BlockInputUnion{InputData: encodedMsg}
 		}
 	}()
diff --git a/pkg/wshutil/wshrpcio.go b/pkg/wshutil/wshrpcio.go
index 00d564d71..5d9dd27ba 100644
--- a/pkg/wshutil/wshrpcio.go
+++ b/pkg/wshutil/wshrpcio.go
@@ -79,10 +79,12 @@ func AdaptMsgChToPty(outputCh chan []byte, oscEsc string, output io.Writer) erro
 		panic("oscEsc must be 5 characters")
 	}
 	for msg := range outputCh {
-		barr := EncodeWaveOSCBytes(oscEsc, msg)
-		_, err := output.Write(barr)
+		barr, err := EncodeWaveOSCBytes(oscEsc, msg)
 		if err != nil {
-			return fmt.Errorf("error writing to output: %w", err)
+			return fmt.Errorf("error encoding osc message (AdaptMsgChToPty): %w", err)
+		}
+		if _, err := output.Write(barr); err != nil {
+			return fmt.Errorf("error writing osc message (AdaptMsgChToPty): %w", err)
 		}
 	}
 	return nil
diff --git a/pkg/wshutil/wshutil.go b/pkg/wshutil/wshutil.go
index 926fa8353..79cdc6080 100644
--- a/pkg/wshutil/wshutil.go
+++ b/pkg/wshutil/wshutil.go
@@ -67,9 +67,13 @@ func makeOscPrefix(oscNum string) []byte {
 	return output
 }
 
-func EncodeWaveOSCBytes(oscNum string, barr []byte) []byte {
+func EncodeWaveOSCBytes(oscNum string, barr []byte) ([]byte, error) {
 	if len(oscNum) != 5 {
-		panic("oscNum must be 5 characters")
+		return nil, fmt.Errorf("oscNum must be 5 characters")
+	}
+	const maxSize = 64 * 1024 * 1024 // 64 MB
+	if len(barr) > maxSize {
+		return nil, fmt.Errorf("input data too large")
 	}
 	hasControlChars := false
 	for _, b := range barr {
@@ -85,7 +89,7 @@ func EncodeWaveOSCBytes(oscNum string, barr []byte) []byte {
 		copyOscPrefix(output, oscNum)
 		copy(output[oscPrefixLen(oscNum):], barr)
 		output[len(output)-1] = BEL
-		return output
+		return output, nil
 	}
 
 	var buf bytes.Buffer
@@ -101,7 +105,7 @@ func EncodeWaveOSCBytes(oscNum string, barr []byte) []byte {
 		}
 	}
 	buf.WriteByte(BEL)
-	return buf.Bytes()
+	return buf.Bytes(), nil
 }
 
 func EncodeWaveOSCMessageEx(oscNum string, msg *RpcMessage) ([]byte, error) {
@@ -112,7 +116,7 @@ func EncodeWaveOSCMessageEx(oscNum string, msg *RpcMessage) ([]byte, error) {
 	if err != nil {
 		return nil, fmt.Errorf("error marshalling message to json: %w", err)
 	}
-	return EncodeWaveOSCBytes(oscNum, barr), nil
+	return EncodeWaveOSCBytes(oscNum, barr)
 }
 
 var termModeLock = sync.Mutex{}
@@ -194,7 +198,11 @@ func SetupTerminalRpcClient(serverImpl ServerImpl) (*WshRpc, io.Reader) {
 	rpcClient := MakeWshRpc(messageCh, outputCh, wshrpc.RpcContext{}, serverImpl)
 	go func() {
 		for msg := range outputCh {
-			barr := EncodeWaveOSCBytes(WaveOSC, msg)
+			barr, err := EncodeWaveOSCBytes(WaveOSC, msg)
+			if err != nil {
+				fmt.Fprintf(os.Stderr, "Error encoding OSC message: %v\n", err)
+				continue
+			}
 			os.Stdout.Write(barr)
 		}
 	}()
diff --git a/yarn.lock b/yarn.lock
index 9740c1282..fd141cfe1 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3493,9 +3493,9 @@ __metadata:
   languageName: node
   linkType: hard
 
-"app-builder-lib@npm:25.1.7":
-  version: 25.1.7
-  resolution: "app-builder-lib@npm:25.1.7"
+"app-builder-lib@npm:25.1.8":
+  version: 25.1.8
+  resolution: "app-builder-lib@npm:25.1.8"
   dependencies:
     "@develar/schema-utils": "npm:~2.6.5"
     "@electron/notarize": "npm:2.5.0"
@@ -3530,9 +3530,9 @@ __metadata:
     tar: "npm:^6.1.12"
     temp-file: "npm:^3.4.0"
   peerDependencies:
-    dmg-builder: 25.1.7
-    electron-builder-squirrel-windows: 25.1.7
-  checksum: 10c0/f1428c330d5220f84d3648ae5dd7a5a812012d14d516d5ac073481e63b8e8668746ae61958851670537b89901bdae64ccf6a3e958ea0ebf1ed10e3d6f00e5b2d
+    dmg-builder: 25.1.8
+    electron-builder-squirrel-windows: 25.1.8
+  checksum: 10c0/e646d4b45872b51ae562788df87024cf0b0c09db66538712837043561712976dbd511ecc56c1172114f676b14518c23b78c412db22173a91cc42f23e88c556d0
   languageName: node
   linkType: hard
 
@@ -5002,11 +5002,11 @@ __metadata:
   languageName: node
   linkType: hard
 
-"dmg-builder@npm:25.1.7":
-  version: 25.1.7
-  resolution: "dmg-builder@npm:25.1.7"
+"dmg-builder@npm:25.1.8":
+  version: 25.1.8
+  resolution: "dmg-builder@npm:25.1.8"
   dependencies:
-    app-builder-lib: "npm:25.1.7"
+    app-builder-lib: "npm:25.1.8"
     builder-util: "npm:25.1.7"
     builder-util-runtime: "npm:9.2.10"
     dmg-license: "npm:^1.0.11"
@@ -5016,7 +5016,7 @@ __metadata:
   dependenciesMeta:
     dmg-license:
       optional: true
-  checksum: 10c0/a449c0afe051a61bee12069d2e02896cef49e0ddbed63920bd74c86ff1e6cd7039b9fb02725cbbcfea87972e5d4bdd8c3bb5514d9469f335c9acc63c9caa6c53
+  checksum: 10c0/a472aba3398664259713f4baf6557509ce0a3832ff49fae2f580e97168a33dcee8f99be2f990a15d795cd28b5477b10fbb8a17bed4f8d410c8181b4b9e2d0063
   languageName: node
   linkType: hard
 
@@ -5123,15 +5123,15 @@ __metadata:
   languageName: node
   linkType: hard
 
-"electron-builder@npm:^25.1.7":
-  version: 25.1.7
-  resolution: "electron-builder@npm:25.1.7"
+"electron-builder@npm:^25.1.8":
+  version: 25.1.8
+  resolution: "electron-builder@npm:25.1.8"
   dependencies:
-    app-builder-lib: "npm:25.1.7"
+    app-builder-lib: "npm:25.1.8"
     builder-util: "npm:25.1.7"
     builder-util-runtime: "npm:9.2.10"
     chalk: "npm:^4.1.2"
-    dmg-builder: "npm:25.1.7"
+    dmg-builder: "npm:25.1.8"
     fs-extra: "npm:^10.1.0"
     is-ci: "npm:^3.0.0"
     lazy-val: "npm:^1.0.5"
@@ -5140,7 +5140,7 @@ __metadata:
   bin:
     electron-builder: cli.js
     install-app-deps: install-app-deps.js
-  checksum: 10c0/19ca437486c9200e10f8e21aff02af6438c786093bd90b383c3b57a68a8de0946e23ac402da9ec28a0626a36db4ec615886edc1c56572374dff26cd86ba035d9
+  checksum: 10c0/9602a19f8c647fb75b07e44dc856012d2b1fe1afcb257ffd24cf17e07d7ae3b51405cf31da10403965ddcc7b194d60aca6bc5d7890e367b1be2ec95219edcbe8
   languageName: node
   linkType: hard
 
@@ -5217,16 +5217,16 @@ __metadata:
   languageName: node
   linkType: hard
 
-"electron@npm:^32.2.0":
-  version: 32.2.0
-  resolution: "electron@npm:32.2.0"
+"electron@npm:^33.0.1":
+  version: 33.0.1
+  resolution: "electron@npm:33.0.1"
   dependencies:
     "@electron/get": "npm:^2.0.0"
     "@types/node": "npm:^20.9.0"
     extract-zip: "npm:^2.0.1"
   bin:
     electron: cli.js
-  checksum: 10c0/28d988a9d05c89e93d70cc790bd53ecc97135cc3fa9efe3617f10b87cdf85ada468d383afd7858bcf8f064aa189ea7f8987e32c7ebaac70bda64e8f9f85621e8
+  checksum: 10c0/cd5e5cef21df2d5e1ffc95cfe90397c984152db54ac1032deb97f1716f778d41f5fa7c83e42bc86ae3c509a6d45f578515a540b6453ebf818310cc303e95f083
   languageName: node
   linkType: hard
 
@@ -11688,8 +11688,8 @@ __metadata:
     css-tree: "npm:^3.0.0"
     dayjs: "npm:^1.11.13"
     debug: "npm:^4.3.7"
-    electron: "npm:^32.2.0"
-    electron-builder: "npm:^25.1.7"
+    electron: "npm:^33.0.1"
+    electron-builder: "npm:^25.1.8"
     electron-updater: "npm:6.3.9"
     electron-vite: "npm:^2.3.0"
     env-paths: "npm:^3.0.0"