feat: self-hosted token sync with Android support; Android IME polish and cursor context - #1133
Open
DepengWang wants to merge 13 commits into
Open
DepengWang wants to merge 13 commits into
DepengWang wants to merge 13 commits into
Conversation
Adds an alternative to GitHub OAuth for encrypted multi-device sync: a user-configurable server origin plus a static bearer token, stored securely via the existing CredentialStore (never in plain preferences). A configured custom token always takes priority over a GitHub session and bypasses the system-proxy preference, since a self-hosted server is the user's own explicitly reachable endpoint. Verified end-to-end (sign-in, vault read, create, enable) against a real self-hosted deployment through the actual desktop UI. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Three root causes blocked every Android restore attempt before it ever inspected a document: - mobile_runtime.rs never called bind_restore_runtime_effects() (desktop does), so runtime_effects() always returned Unsupported during restore. - capture.rs required the UI-preferences extension slot to already be populated, which only happens after a user manually changes locale/font scale post-enable; a device that never touched that setting (true for a fresh install) could not create or restore any snapshot at all. - isTauri was a frozen const computed once at module-eval time; a mid-session Android webview rebuild (ensure_main_webview_window) could permanently route all subsequent backend calls through the browser-preview mock. Switched the two gating call sites to a live isTauriNow() check. Also registers sync_custom_server_origin (added for the self-hosted-server feature) in the preference registry as Excluded — it's a manually-entered, per-device value and must never be classified as syncable in any form.
…artup SyncServiceConfig.origin was a fixed snapshot taken once at backend construction; saving a new self-hosted server address in Settings updated the preferences file but never reached the already-running sync service, so the new address only took effect after a full app restart (affects both Windows and Android, same shared service.rs). Added SyncServiceData::custom_server_origin() (reads the live preferences store) and a private EncryptedSyncService::origin() helper that every connection attempt now calls instead of trusting config.origin directly.
zh-CN/zh-TW only — the description under "加密云同步" said sync happened via GitHub account only, no longer true now that a self-hosted server's static token is a supported sign-in path.
The account badge always showed "@login", which reads like a GitHub handle. When a custom server origin is configured, show "login@host" instead so a self-hosted account is visually distinct from a GitHub one.
The component imports both from '../../lib/ipc', but the test harness's dependency map only spread '../../lib/ipc/cloud-sync-e2ee', leaving the two functions undefined and crashing render with "readCredential is not a function". This broke Windows and macOS CI checks on PR Open-Less#1133. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011h9QUkoFtCoDLsJjRDB6dX
Raw / Quick notes / Cloud notes now keep their accent color from recording through the thinking step: status text, the thinking dots, the hint row and the link indicator all follow the armed mode instead of dropping back to gray/blue. Cloud notes also keeps it (and the dots) up during its webhook submit. Keyboard height and raise move out of the permanently docked panel at the bottom of keyboard settings into a bottom sheet with stepper sliders, a reset, and the 1:1 footprint flush with the screen bottom. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds ImePrivacyPolicy: the gate (TYPE_NULL, password fields, IME_FLAG_NO_PERSONALIZED_LEARNING, sensitive packages) and the single, no-retry read of the text around the caret. The readers are never invoked while the cursorContextEnabled switch is off or a gate rejects the editor, and a null or throwing InputConnection degrades to no context. A surrogate pair cut by the read window is trimmed so no half character crosses JNI. Kept separate from ImeLearningPolicy: learning decides what stays on the device, cursor context may be sent to the polish LLM provider. The accessibility vocabulary observer now shares the same sensitive package list instead of carrying its own copy. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The IME snapshots the text around the caret through its own InputConnection when a recording starts and sends it with the "start" command as structured frontApp / cursorBefore / cursorAfter fields. Stop, cancel and cloud commands never resend it. native_bridge trims the two sides with Core's existing window budget (new window_from_split helper, same 80/20 rule as the desktop readers) and builds the envelope input with the shared cursor_context_input, so Android has no prompt format of its own. Logs carry character counts and the package name only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Quick notes and cloud notes already keep polish.cursor_context when the output target changes. This locks that in, together with the other half of the rule: the context informs polish but never becomes stored content. The Core test runs dictation, quick note and cloud note sessions with a caret snapshot, checks the polisher saw it each time, and scans the whole data directory for the snapshot text afterwards. The contract test covers the Kotlin and JNI side: InputConnection source, gate order, start-only payload, metadata-only logs, webhook body and history schema. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Shows the Cursor context toggle on Android as well as macOS. The description in all 8 locales now names Android, says dictation, quick notes and cloud notes use the context without storing it, and states that a cloud polish model receives it with the polish request. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Covers the Android read path, what the context is and is not used for, the extra Android gates, and that the floating overlay does not read cursor context yet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The macOS jobs on the previous commit were cancelled before running because no hosted runner was acquired. No code change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
Summary
cloud_sync_e2ee): a user-configurable server origin + a static bearer token, stored in the same secure credential vault as the GitHub token. Motivated by GitHub connectivity instability for users in regions where github.com is unreliable — the sign-in/data path never touches github.com when a custom token is configured./v1/auth/token,/v1/capabilities, and/v1/me/*endpoints this PR's client talks to.runtime_effects()was permanentlyUnsupportedduring any restore;isTauriconstant computed once at module-eval time could get stuckfalseforever after a mid-session Android webview rebuild, silently routing every subsequent backend call through the browser-preview mock.syncCustomServerOrigin, the self-hosted server address) is explicitly registered asExcludedfrom sync — like the token itself, it's a manually-entered, per-device value and must never be classified as syncable in any form.service.rs): saving a new custom server origin/token in Settings previously only took effect after a full app restart, because the live service cached the origin once at backend-construction time. The service now re-reads the preference on every connection attempt.user@hostinstead of@userwhen signed in via a self-hosted server, to visually distinguish it from a GitHub account.Android IME polish (Kotlin only, independent of the sync changes)
Android cursor context (IME, opt-in)
InputConnectionand hands Core the two sides as structured text. Core applies the existing 600-character window and the shared<cursor_context>prompt, so there is no Android-specific prompt.TYPE_NULL, editors flaggedIME_FLAG_NO_PERSONALIZED_LEARNING, or known password managers / Termux. A failed read degrades to no context and dictation continues.Platform support
openless-core/service.rslayer with no platform-specific gating beyond the existing Android restore-path wiring above, so they likely build everywhere, but no macOS/Linux verification has been done in this PR.Testing
create → deleteround trip verified against a real self-hosted deployment (not just the in-process test mock): token auth, metadata read, real encrypted upload, and delete, with revision moving0→1→2as expected.cargo test -p openless-core --lib: 1071 passed, 0 failed, 1 ignored (pre-existing, unrelated).tsc --noEmit: clean.:app:compileArmDebugKotlinclean, arm64 debug APK built and manually checked on a real device (Xiaomi M2011K2C).openless-corelib tests pass, including a new test that runs dictation, quick note and cloud note sessions with a caret snapshot, checks the polisher saw it, and scans the data directory to confirm it was not persisted; 7 Kotlin unit tests for the privacy gate; a new contract test. On a Xiaomi phone (Android 14) in WeCom, from a local build that also includes feat(windows): UIA cursor context; pace SendInput keystrokes #1137: 62 characters captured in 8 ms and delivered to the polish request. Not yet verified on a device: caret in the middle of text, quick and cloud notes, password fields, other apps.Not covered by this PR
optional build metadata ... cannot be greater than 65535) — unrelated, worked around locally with--no-bundle, not fixed here.Screenshot
Windows, Settings -> Permissions & data. The encrypted sync card is signed in with a static token against a self-hosted server: the account badge shows
user@hostinstead of@user, and the vault is synced. Taken from a local build that also has #1137 merged, which is why the Cursor context toggle appears above it.