Skip to content

Merge Horizon v2.4.0-beta.0 and unlock Device Sync - #19

Merged
Kannamoris merged 133 commits into
developmentfrom
merge/horizon-prerelease
Sep 6, 2026
Merged

Merge Horizon v2.4.0-beta.0 and unlock Device Sync#19
Kannamoris merged 133 commits into
developmentfrom
merge/horizon-prerelease

Conversation

@Kannamoris

@Kannamoris Kannamoris commented Sep 6, 2026

Copy link
Copy Markdown
Collaborator

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

  • i18n overhaul: named-placeholder and CLDR-plural formatting (l() / lp()), locale files renamed to BCP47 codes (en_us.json becomes en-US.json, en_uwu.json becomes en-x-uwu.json).
  • Toolchain: Electron 42, Node 24, pnpm 11, Ghostery 2.18.2.
  • Chat-sync: the desktop server side plus the LAN log-sync protocol (handshake, log transfer, finish; body/uncompressed caps; message-type validation; session locking and recovery).
  • The always-incognito toggle, plus assorted ad/filter, log-viewer, and UI fixes.

Mobile reconciliation

  • 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 always shown now, 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 via a mobile shim and webpack alias, and hid its Manage Data section on mobile. Solstice only ever acts as a sync client.
  • Regenerated pnpm-lock.yaml against the merged manifest.
  • Preserved the Solstice mobile touchpoints that auto-merged (nativeKeepalive and receivedAt, getKey iOS keyCode fallback, profile_api reconnect guard, Solstice branding).

Also included

  • 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.
  • Fix: use the global TextDecoder in log-backup so the mobile bundle boots (the util polyfill does not expose TextDecoder, which was blanking the screen at load).

Verification

  • pnpm run build:mobile:dist clean (typecheck + bundle).
  • node mobile/sync/run-tests.mjs green.
  • Android debug APK built and booted on the emulator; login screen renders with Solstice branding.
  • Device Sync exercised end-to-end on device: paired with desktop Horizon over LAN and pulled the full multi-character history (1,476 conversations, verified 1:1 against the desktop store, no duplication).

Merge note

Please merge with a real merge commit, not squash, so c16f331d stays in development'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_VERSION set to 2.4.0-beta.0. No release is cut on merge; a pre-release only happens when a v* tag is pushed afterward.

SmileyTatsu and others added 30 commits May 26, 2026 13:00
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
Kannamoris and others added 29 commits August 31, 2026 20:50
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.
@Kannamoris
Kannamoris merged commit 7efd1e7 into development Sep 6, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.