Merge Horizon v2.4.0-beta.0 and unlock Device Sync - #19
Merged
Conversation
Adds a Device Sync section to the Data Manager that starts a single-use local HTTP server and shows a QR code. A Solstice device signed into the same account scans it and both clients exchange their chat logs, ending up with the union of all messages. Only logs are transferred; settings, drafts and pins are untouched. - All request/response bodies are AES-256-GCM encrypted with a key that only leaves the machine inside the QR code; requests carry a bearer token compared in constant time. Sessions are single-use, expire after 10 minutes unpaired and abort after repeated bad auth attempts. - New message-level union merge (electron/services/sync/log-merge.ts) dedupes on (time, type, sender, text), rewrites the binary log and rebuilds the .idx day index in one pass. Idempotent by construction. - Wire format is the existing export zip layout (JSON logs) plus a logs-names.json per character so channel titles survive to new devices. - Sync requires being signed out, same as the zip importer, and aborts if a character connects mid-session. - Protocol spec for the Solstice implementation in docs/log-sync-protocol.md; scripts/sync-client-sim.mjs is a dev client for exercising the server without a phone.
Replace the shared colorblind gender palette with two CVD-tuned palettes (dark and light themes, selected by background lightness) so every pair of gender colors stays distinguishable under protanopia/deuteranopia, and restyle message highlights with a blue tint and amber border instead of the green-derived colors that red/green colorblind users cannot see.
Always extract and store the HQ portrait override (and clear it when a profile no longer has one), and decide at render time whether to show it, so toggling the setting updates avatars everywhere immediately. Also push the setting-gated avatar to the shell window on settings changes so the character tab updates too.
fix: extend the channel prune grace period past the ping threshold
isValidLogMessage capped m.type at 0xff, the byte width of the on-disk u8 type field, rather than the semantic ceiling. The Conversation.Message.Type enum and the log-sync protocol only define types 0 through 6 (Message, Action, Ad, Roll, Warn, Event, Bcast), so a peer could merge messages with types the client can never render. Bound the check to a named MAX_MESSAGE_TYPE constant instead.
The renderer-local activeServer check in startSyncSession is only assigned after an await, so a rapid double-click could get two calls past it, and a second Data Manager window has its own copy of that variable, so it never guarded across windows. The main-process sync lock is the authoritative, cross-window guard (taken synchronously before the await), but it returned a bare boolean, so a redundant start that lost the race was reported as the misleading lockedWhileConnected error. Return a reason from sync-lock-acquire so a start that finds a session already running is a silent no-op while a genuine connected-character refusal still surfaces the error, and drop the racy renderer-local guard.
Drop the duplicated LogMessage type, binary parse/serialize and index-name reader from the sync log merge and delegate to log-backup's binaryLogToJson, jsonLogToBinary, readLogIndexName and JsonLogMessage. jsonLogToBinary also null-guards sender, which the removed serializer did not.
- Copy payload feedback - Warning for beta release
…interface function to force a connection error, for safeguards.
chore: Update Ghostery (and the others) to 2.18.2 (was 2.18.1)
Brings in Horizon's 2.4.0-beta.0 line: the i18n named-placeholder and plural overhaul (locale files renamed to BCP47 codes, e.g. en_us.json becomes en-US.json), the Electron 42 / Node 24 / pnpm 11 toolchain bump, the desktop chat-sync feature and its LAN log-sync protocol, the always-incognito toggle, and assorted ad/filter, log-viewer, and UI fixes. Mobile reconciliation done as part of the merge: - Re-applied the Horizon to Solstice product rebrand onto the new-format locale catalogs (titles, settings tab, export/import, tips, sync strings) across en-US, de, es, fr, hu, it, ru, and en-x-uwu. - Unlocked Device Sync on mobile: the character-select entry point is now always shown and the hidden filter-phrase gate (digest, watcher, persisted flag) is removed. Mobile syncs as a client via mobile/sync/. - Kept the desktop Device Sync server (sync-ui, which pulls in worker_threads and an http server) out of the WebView bundle with a mobile shim, and hid its Manage Data section on mobile so the desktop-only server UI never surfaces there. - Regenerated pnpm-lock.yaml against the merged manifest (kept jsqr and node-polyfill-webpack-plugin, took Electron 42 and Ghostery 2.18.2). - Preserved the Solstice mobile touchpoints that auto-merged (nativeKeepalive and receivedAt, getKey iOS keyCode fallback, profile_api reconnect guard, Solstice branding). Also carries a cherry-pick of the mobile sync archive-size limits (bounded upload and oversized-archive handling in mobile/sync/), which complements Horizon's own sync archive-size validation.
electron/services/log-backup.ts imported TextDecoder from Node's 'util'. The mobile WebView bundle polyfills 'util' but that polyfill does not expose TextDecoder, so the named import resolved to undefined and the module-level `new TextDecoder('utf-8', ...)` threw at load. That aborted chat.js before window.setupPlatform was defined and left the app on a blank white screen (found while testing the Horizon v2.4.0-beta.0 merge on the Android emulator).
TextDecoder is a WHATWG global in both Node 18+ and the WebView, so use the global and drop the import. It works unchanged on desktop. Verified on the emulator: the login screen renders again.
versionCode 52 to 53, versionName and package version to 2026.9.0 (a new CalVer line for the September pre-release), and HORIZON_BASE_VERSION to 2.4.0-beta.0 to reflect the merged Horizon base.
The iOS build reads its marketing version from mobile/package.json (ios.yml), separate from the root package.json that drives the JS SOLSTICE_VERSION. The earlier bump missed this file, so the iOS IPA and SideStore source would still report 2026.6.4 while Android and the in-app version read 2026.9.0. Bump it to match.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Brings Solstice up to Horizon's 2.4.0-beta.0 line and turns on Device Sync for mobile.
What this pulls in from Horizon
l()/lp()), locale files renamed to BCP47 codes (en_us.jsonbecomesen-US.json,en_uwu.jsonbecomesen-x-uwu.json).Mobile reconciliation
mobile/sync/.worker_threadsand an http server) out of the WebView bundle via a mobile shim and webpack alias, and hid its Manage Data section on mobile. Solstice only ever acts as a sync client.pnpm-lock.yamlagainst the merged manifest.nativeKeepaliveandreceivedAt,getKeyiOS keyCode fallback,profile_apireconnect guard, Solstice branding).Also included
mobile/sync/), which complements Horizon's own sync archive-size validation.TextDecoderinlog-backupso the mobile bundle boots (theutilpolyfill does not exposeTextDecoder, which was blanking the screen at load).Verification
pnpm run build:mobile:distclean (typecheck + bundle).node mobile/sync/run-tests.mjsgreen.Merge note
Please merge with a real merge commit, not squash, so
c16f331dstays indevelopment's ancestry. Squashing would reset the merge-base and force the next Horizon merge to re-conflict the whole beta.0 range.Version is bumped in this PR to 2026.9.0 (a new CalVer line for the September pre-release), versionCode 53, with
HORIZON_BASE_VERSIONset to2.4.0-beta.0. No release is cut on merge; a pre-release only happens when av*tag is pushed afterward.