# How User Dictionary Synchronization Works in Rime Wanxiang: A Technical Deep Dive

> Learn how user dictionary synchronization works in Rime Wanxiang. Discover the technical details of merging per-device export files in a shared sync directory using last-write-wins.

- Repository: [amzxyz/rime_wanxiang](https://github.com/amzxyz/rime_wanxiang)
- Tags: deep-dive
- Published: 2026-02-24

---

**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`](https://github.com/amzxyz/rime_wanxiang/blob/main/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`](https://github.com/amzxyz/rime_wanxiang/blob/main/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`](https://github.com/amzxyz/rime_wanxiang/blob/main/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`](https://github.com/amzxyz/rime_wanxiang/blob/main/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")` from [`wanxiang/lua/lib/userdb.lua`](https://github.com/amzxyz/rime_wanxiang/blob/main/wanxiang/lua/lib/userdb.lua))
- Every other device's export file listed in [`sequence_device_list.txt`](https://github.com/amzxyz/rime_wanxiang/blob/main/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:
1. **Line 1**: `/user_id` metadata
2. **Line 2**: `/device_name` metadata  
3. **Remaining lines**: Identical TAB-separated records as the local DB

The manifest file [`sequence_device_list.txt`](https://github.com/amzxyz/rime_wanxiang/blob/main/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`](https://github.com/amzxyz/rime_wanxiang/blob/main/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:

```lua
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:

```lua
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:

```lua
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_at` timestamps, implemented in `collect_latest_from_all_sources`
- Configuration occurs via [`installation.yaml`](https://github.com/amzxyz/rime_wanxiang/blob/main/installation.yaml), with fallback to `<user_data_dir>/sync`
- The pipeline runs automatically on startup via `init_once` and periodically via `maybe_export` with 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`](https://github.com/amzxyz/rime_wanxiang/blob/main/installation.yaml) within the user data folder. The `_sync_dir` function in [`wanxiang/lua/super_sequence.lua`](https://github.com/amzxyz/rime_wanxiang/blob/main/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`](https://github.com/amzxyz/rime_wanxiang/blob/main/installation.yaml) in your Rime user data directory and add:

```yaml
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.