Skip to content

Add syncing settings to a GitHub Gist - #4950

Open
xianghongai wants to merge 1 commit into
philc:masterfrom
xianghongai:master
Open

xianghongai wants to merge 1 commit into
philc:masterfrom
xianghongai:master

Conversation

@xianghongai

@xianghongai xianghongai commented Oct 12, 2026 •

Copy link
Copy Markdown

Description

This adds optional syncing of Vimium's settings to a GitHub Gist, revisiting #4084 (closed as not planned). I know that issue raised two concerns, so I've tried to address both directly in the design below, and to keep the feature entirely opt-in and isolated from the existing settings path.

Why a Gist? chrome.storage.sync only syncs within one browser vendor and one account. Many Vimium users run several browsers (Chrome + Firefox), several profiles (work / personal), or machines where browser sync is disabled by policy. Today they keep their key mappings in step by hand with the backup/restore file. A Gist is a natural home for that file: it's versioned, private when secret, and already familiar to the people who write custom key mappings. #2719 asked for essentially the same thing.

Addressing the concerns in #4084

1. "Pasting a GitHub token is clunky UX." Vimium's audience already edits key mappings, writes CSS for link hints and configures search engines by URL template, so creating a token is well within reach. The options page also makes it a two-click flow:

  • Create a token on GitHub links to github.com/settings/tokens/new?description=Vimium%20Gist%20Sync&scopes=gist. GitHub's form opens with the note filled in and only the gist scope ticked. The hint next to the field tells the user to set Expiration to "No expiration" (GitHub doesn't support pre-filling that), then click Generate token and paste it.
  • Create a secret gist does the rest automatically in one click: it creates a secret gist via the API, uploads the current settings to it, and fills in and saves the Gist ID. The user never has to visit gist.github.com or copy an ID. Users who already have a gist can paste its ID or full URL instead, and the ID is extracted from the URL automatically.

2. "Persistence would have to handle network failures." It doesn't need to, because the Gist is never the source of truth. Settings are still saved to chrome.storage.sync exactly as today, and Settings is not modified. Gist sync is a separate layer on top:

  • Manual upload/restore show success or failure inline on the options page.
  • Auto-upload (opt-in) is a debounced, best-effort push from the background page. A failure is recorded in a status line and logged; nothing is lost, and the next change or a manual upload retries.

Design

flowchart LR
  subgraph Browser
    O["Options page<br/>(pages/options.js)"]
    S[("chrome.storage.sync<br/>Vimium settings")]
    L[("chrome.storage.local<br/>token, gist ID,<br/>auto-upload flag, status")]
    B["Background page<br/>(main.js)"]
    G["gist_sync.js"]
  end
  GH[("GitHub Gist<br/>vimium-options.json<br/>+ revision history")]

  O -- "Save (unchanged)" --> S
  S -- "Settings 'change' event" --> B
  B -- "debounced auto-upload<br/>(opt-in, skipped if unchanged)" --> G
  O -- "Upload / Restore /<br/>Create gist / History" --> G
  G -- "reads config" --> L
  G -- "PATCH /gists/:id<br/>(overwrite file)" --> GH
  GH -- "GET /gists/:id<br/>GET /gists/:id/:sha" --> G
  G -- "Settings.setSettings()<br/>(existing migration path)" --> S
Loading
  • Same file format as backups. The gist holds a single vimium-options.json produced by the same serializer as Download backup. prepareBackupSettings() now calls the shared serializeSettings(), so a gist file and a backup file are interchangeable. Other files in the gist are left untouched.
  • Restore reuses the existing path. Restoring calls Settings.setSettings(), just like Restore from a backup file does, so older settings files go through the normal migrations.
  • Secrets never touch settings. The token and gist ID live in chrome.storage.local. They're not in chrome.storage.sync, so they are never in backups, never uploaded to the gist, and never synced by the browser. The options page fields are kept out of the settings form, so typing in them doesn't enable Save changes.
  • Upload overwrites; history is the safety net. Uploading replaces the remote file. Nothing is merged, so there's no conflict logic. Every upload becomes a Gist revision, and Version history lists the 10 most recent revisions with a Restore button for each.
  • No new permissions. <all_urls> already allows requests to api.github.com, so manifest.json is unchanged and this works on Chrome and Firefox alike.

Changes

File Change
background_scripts/gist_sync.js New module (~250 lines): config, serializeSettings, upload, download, createGist, testConnection, listRevisions, restoreRevision, debounced auto-upload
background_scripts/main.js +4 lines: run auto-upload on the Settings "change" event
pages/options.{html,js,css} A "Gist sync" block under Backup and Restore
tests/unit_tests/gist_sync_test.js 12 tests with fetch stubbed
tests/unit_tests/test_chrome_stubs.js Fix get() when given an array of keys (for (key of …) referenced an undeclared variable)

Testing

  • ./make.js test-unit: 232/232 pass. ./make.js check is clean.
  • Tested end to end against a real secret gist: test connection, create gist, upload, restore, auto-upload (including skipping an unchanged upload), listing revisions, and restoring an older revision.
  • Loaded unpacked in Chrome and checked the options page UI.

Size

I'm aware this is well over the ~50 LOC guideline. About 170 of the lines are tests, and the feature is self-contained: it doesn't change any existing behavior unless a user opts in. If you'd prefer a smaller first step, it could be split into (1) manual upload/restore and (2) auto-upload plus history. I'm happy to rework it, or to keep it in my fork if it's out of scope for mainline Vimium.

Refs #4084, #2719.

@xianghongai

xianghongai commented Oct 12, 2026 •

Copy link
Copy Markdown
Author

Here's what the new section looks like at the bottom of the options page, under Backup and Restore (the Gist ID is a placeholder):

Gist sync settings on the Vimium options page

Setting it up from scratch:

  1. Click Create a token on GitHub. GitHub's form opens with the note filled in and only the gist scope ticked. Set Expiration to "No expiration", click Generate token, and paste the token here.
  2. Click Create a secret gist. Vimium automatically creates the secret gist, uploads the current settings, and fills in and saves the Gist ID, so there's nothing to set up on gist.github.com. Or, if you already have a gist, paste its ID or URL.
  3. Use Upload to gist / Restore from gist, or tick the checkbox to upload automatically on save. Each operation shows its progress and then a green ✓ or red ✗ result right below the buttons. Version history lists the last 10 revisions, and each one can be restored.

The new controls follow the layout of the existing settings rows, and none of them affect anything unless the user fills them in.

The options page can now sync settings with a vimium-options.json file
in a secret gist, using the same format as the backup file:

- Upload to the gist (overwrites the remote file), and restore from it.
- Optionally upload automatically whenever settings change.
- Create a secret gist from the options page, or paste an existing one.
- A link to GitHub's token form, pre-filled with the "gist" scope.
- List the gist's 10 most recent revisions and restore any of them.

The GitHub token and gist ID are kept in chrome.storage.local so they're
never included in the settings, backups, or the gist itself.

Also fix the chrome.storage test stub's get() when given an array of keys.
@quintrino

Copy link
Copy Markdown

Hi @xianghongai, could you update the README file on this PR to include the steps needed to utilize it?

It might also be helpful to include links/screenshots on how to generate the gist token.

@xianghongai

Copy link
Copy Markdown
Author

Hi @xianghongai, could you update the README file on this PR to include the steps needed to utilize it?

It might also be helpful to include links/screenshots on how to generate the gist token.

Thanks for the suggestion and for taking a look!

I’d actually lean against adding this to the main README.md. A few thoughts on why:

  1. Scope & Focus: Vimium’s README is already very well-scoped around its core functionality. Gist sync is an optional utility/enhancement rather than a core feature.

  2. Documentation Clutter: Adding extra setup steps or screenshots would lengthen the README and distract from the primary documentation. Keeping the main docs concise and focused feels cleaner.

  3. Target Audience: Vimium users are generally technical and already familiar with GitHub/Gists, so extensive documentation here might be redundant.

The comment and screenshots I left on the PR were mainly to provide context for the maintainers to evaluate the implementation.

Really appreciate your feedback and input, though!

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.

2 participants