How User Dictionary Synchronization Works in Rime Wanxiang: A Technical Deep Dive
User dictionary synchronization in Rime Wanxiang works by maintaining per-device export files in a shared sync directory and automatically merging them using a last-write-wins strategy based on modification timestamps.
The rime_wanxiang input method implements a robust cross-device synchronization system for user phrase ordering data. According to the source code in wanxiang/lua/super_sequence.lua, the engine manages a local UserDb for sequence data while coordinating with external files to ensure consistent dictionary state across multiple machines. This article examines the technical implementation of the synchronization pipeline, data structures, and conflict resolution mechanisms.
How the Synchronization Pipeline Works
The entire synchronization logic resides in wanxiang/lua/super_sequence.lua. The process follows a deterministic 10-step pipeline that runs automatically on startup and periodically during usage.
Configuration and Directory Resolution
The pipeline begins by reading installation.yaml from the user data folder via _read_installation_yaml (lines 87-104). This file optionally contains an installation_id and a custom sync_dir path. If these values are absent, the system falls back to the default location <user_dir>/sync, resolved by the _sync_dir function (lines 107-116). The path is normalized via _normalize_path and stored internally.
Device Identification
The _detect_device_name function (lines 133-144) determines the current device's identity by sanitizing the installation_id from the YAML file. If no ID exists, it derives the name from the first entry in the manifest file sequence_device_list.txt. This device name becomes the suffix for per-device export files.
Export File Management
Before merging, the system ensures local infrastructure exists via _ensure_export_file (lines 24-42). This creates the manifest file if missing and initializes a per-device export file at <sync_dir>/sequence_<device>.txt with metadata headers containing the user ID and device name.
Pending in-memory adjustments are flushed to disk via flush_pending (lines 50-61), which writes any uncommitted sequence changes to the export file. A background task controlled by maybe_export (lines 64-70) triggers these writes periodically with a default export_interval of approximately 1.2 seconds, preventing excessive disk I/O during rapid typing.
Conflict Resolution and Merging
The core synchronization logic occurs in collect_latest_from_all_sources (lines 306-336). This function reads two data sources:
- The local UserDb (accessed via
userdb.LevelDb("lua/sequence")fromwanxiang/lua/lib/userdb.lua) - Every other device's export file listed in
sequence_device_list.txt
For each entry in the format <input>\t<i=…>, the system compares updated_at timestamps and applies a last-write-wins (LWW) strategy. The entry with the greatest timestamp wins deterministically, eliminating the need for network handshakes or server coordination.
Database Update
After determining the authoritative state, rewrite_export_from_latest (lines 389-421) regenerates the local device's export file to reflect the merged data, updating headers and all records. Finally, apply_latest_to_db (lines 482-498) overwrites the local UserDb with the merged records, removing any entries where fixed_position equals zero.
Data Structures and File Formats
Understanding the underlying data formats clarifies how the system maintains consistency between the local database and sync files.
Local UserDb Format
The local database is created through userdb.LevelDb("lua/sequence"). Each key-value pair stores a single input string mapped to a TAB-separated list of adjustment items:
<input>\ti=<item> p=<fixed_position> o=<offset> t=<updated_at>\t...
This format tracks phrase ordering preferences, offsets, and precise modification timestamps for conflict detection.
Sync Export File Format
Per-device export files follow the naming convention sequence_<device>.txt and reside in the sync directory. These are plain text files containing:
- Line 1:
/user_idmetadata - Line 2:
/device_namemetadata - Remaining lines: Identical TAB-separated records as the local DB
The manifest file sequence_device_list.txt maintains a simple list of all sequence_*.txt files in the directory, enabling the system to discover peer devices during the merge phase.
Triggering Mechanisms
The synchronization process initiates through two primary triggers defined in wanxiang/lua/super_sequence.lua.
Startup Initialization: The init_once function (lines 401-407) runs automatically when the processor initializes. It executes the full pipeline—ensuring export files exist, flushing pending changes, collecting latest states, rewriting exports, and applying updates to the local database. It also forces an immediate export via seq_data.maybe_export(true).
Periodic Background Sync: During active usage, the filter calls seq_data.maybe_export(false) after each adjustment. This method respects the export_interval throttle, ensuring disk writes occur no more frequently than every 1.2 seconds while maintaining near-real-time consistency.
Practical Code Examples
Manually Triggering a Full Resync
For custom scripts or debugging, you can force a complete synchronization cycle:
local super_seq = require("super_sequence")
local seq_data = super_seq.seq_data
-- Force immediate export of pending changes
seq_data.maybe_export(true)
-- Re-read all devices and merge
local latest = super_seq.collect_latest_from_all_sources()
super_seq.rewrite_export_from_latest(latest)
super_seq.apply_latest_to_db(latest)
Note that while these functions are module-local in the standard distribution, custom plugins can require the module and access them as shown above.
Inspecting the Sync Directory
To programmatically locate the current sync folder from within a Rime script:
local super_seq = require("super_sequence")
local paths = super_seq.seq_data._current_paths()
local sync_dir = paths[1] -- First return value is the sync directory
print("Sync folder:", sync_dir)
The _current_paths() function returns the sync directory, device name, and manifest paths as determined by _sync_dir() and _detect_device_name().
Adding a New Device to the Manifest
While the system updates the manifest automatically, you can manually register a new device:
local super_seq = require("super_sequence")
local dir, dev, _, _, manifest = super_seq.seq_data._current_paths()
local new_device = "laptop_work"
local lines = super_seq._read_lines(manifest)
local filename = "sequence_" .. new_device .. ".txt"
-- Check if already registered
local exists = false
for _, line in ipairs(lines) do
if line == filename then exists = true break end
end
if not exists then
table.insert(lines, filename)
super_seq._write_lines(manifest, lines)
end
Summary
- User dictionary synchronization in rime_wanxiang relies on a shared sync directory containing per-device export files named
sequence_<device>.txt - The system uses last-write-wins conflict resolution based on
updated_attimestamps, implemented incollect_latest_from_all_sources - Configuration occurs via
installation.yaml, with fallback to<user_data_dir>/sync - The pipeline runs automatically on startup via
init_onceand periodically viamaybe_exportwith a 1.2-second throttle - Local data uses
userdb.LevelDb("lua/sequence")while sync files use plain text with identical record formats
Frequently Asked Questions
Where does Rime Wanxiang store sync files?
By default, Wanxiang stores synchronization data in <user_data_dir>/sync. You can customize this location by setting sync_dir: "/your/path" in installation.yaml within the user data folder. The _sync_dir function in wanxiang/lua/super_sequence.lua handles this resolution logic.
How does Wanxiang resolve conflicts between devices?
The engine employs a last-write-wins (LWW) strategy. When collect_latest_from_all_sources processes entries from multiple devices, it compares the updated_at timestamp in each record format (<input>\ti=<item>...t=<updated_at>) and keeps only the newest version. This deterministic approach requires no network coordination or server authority.
Can I customize the sync directory location?
Yes. Create or edit installation.yaml in your Rime user data directory and add:
sync_dir: "/absolute/path/to/sync/folder"
The _read_installation_yaml function (lines 87-104) parses this value, and _sync_dir uses it instead of the default <user_dir>/sync location. The path must be absolute and will be normalized by _normalize_path.
How often does synchronization occur?
Synchronization happens continuously in the background. init_once runs a full merge when the engine starts. During operation, maybe_export triggers incremental saves no more frequently than every 1.2 seconds (controlled by export_interval). This balances data consistency with performance, ensuring changes propagate to disk without blocking input processing.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →