Maintain Prateek's chezmoi-managed Yojam browser-router config. Use for desired mutations, disappearing or disabled rules, schema bumps, new rule UUIDs, or Mac onboarding. Edit the desired fragment, never the live config; installation belongs to chezmoi-management.
Resources
2Install
npx skillscat add prateek/dotfiles/yojam-config Install via the SkillsCat registry.
Yojam Config
Yojam is the OSS browser router installed via the cask inhome/.chezmoidata/packages.toml. Its live config is one JSON file at~/Library/Application Support/Yojam/config.json that Yojam watches and
re-imports on external writes.
We don't capture that file whole. Yojam fills every key from upstream
defaults via decodeIfPresent, so the file you write only needs to
contain your mutations. Two source files do that:
home/.chezmoiassets/yojam-config.json— the desired
fragment. Hand-authored, small, contains only the keys that differ
from upstream defaults. Plain JSON (no Go template directives), so
it lives under.chezmoiassets/and loads viaincludeper
thechezmoi-managementskill →references/app-config.md.home/Library/Application Support/Yojam/modify_config.json.tmpl
— themodify_stub. AuvPython script that reads live on stdin,
merges the fragment over it, writes the result to stdout. Same
pattern ashome/dot_codex/modify_private_config.toml.tmplandhome/dot_claude/modify_private_settings.json.tmpl.
This skill covers the config side. Cask install lives in
the chezmoi-management skill → references/packages-and-secrets.md.
Universal Rules
- The fragment is deltas-only. Upstream
SettingsExportdecodes
every key withdecodeIfPresentplus a default, so anything you
omit reverts to that default. Carry only what differs. - The merge respects upstream identity.
browsersandemailClientsmerge bybundleIdentifier(stable across machines);rulesmerge byid(UUID). Top-level scalars: desired wins.
Unmatched live entries (Yojam's auto-discovered browsers, learned
domain churn) pass through untouched. Inside a matched entry the
merge isdict.update— fields present in desired overwrite live,
fields absent in desired are preserved on the live entry. The
fragment cannot delete a key from live by omission, and cannot
resetenabled: falseback on without an explicit"enabled": true. See the Bisect workflow for the recovery flow. - The import-time security pass silently disables some shapes.
Five triggers coveringbundleIdentifier,targetBundleId,customLaunchArgs,ruleCustomLaunchArgs, andregexpatterns
— enumerated in the Bisect workflow below. - Rule
idmust be stable. Yojam's decoder requiresidon
every Rule, AND the merge keys off it: a fresh UUID per apply
would append a duplicate rule each time. Mint withuuidgenonce,
then leave it. chezmoi re-adddoes NOT round-trip this file. The source is
amodify_script, not a captured JSON file. Capturing live withre-addwould either error or replace the script with raw JSON.
Edit the fragment by hand instead.
Workflows
Add or change a mutation
Edit home/.chezmoiassets/yojam-config.json, then:
chezmoi diff "$HOME/Library/Application Support/Yojam/config.json"
chezmoi applyThe modify stub merges your fragment into whatever Yojam has written.
For a top-level scalar, just add the key. For a rule, mint a new UUID:
uuidgenPaste it as the rule's id. Set targetBundleId to the destination
app's reverse-DNS bundle ID; set targetAppName to its display name.
Yojam needs only id to decode a rule — every other field has a
sensible default (enabled: true, priority: 100, matchType: .domain, etc.).
Debug a setting you set in the GUI
Yojam writes the GUI change into the live config.json. The nextchezmoi apply runs the modify stub, which preserves your live change
unless the fragment overrides that exact field. If the change
should be portable, copy the relevant key(s) into the fragment. If
not, leave the fragment alone and the change stays local.
Bisect a "missing rule"
If a rule visible in the source fragment is absent from the GUI
after chezmoi apply, the import-time security pass inSettingsStore.importJSON dropped or disabled it. Five triggers, all
silent:
- Browser/email entry with path-prefixed
bundleIdentifier(starts
with/) → silently disabled. - Browser/email entry with non-null
customLaunchArgs→ silently
disabled. - Rule with path-prefixed
targetBundleId(starts with/) →
silently disabled. - Rule with non-null
ruleCustomLaunchArgs→ silently disabled. - Rule with
matchType == .regexand a pattern that failsRegexMatcher.isValid→ dropped entirely.
Fix the offending field in the fragment, then chezmoi apply.
If the rule was already imported once, live carries enabled: false
plus the offending field, and the deltas-only merge cannot clear that
field by omission (Universal Rule #2). Either delete the rule in the
Yojam GUI before re-applying, or jq del-edit live; either way, add"enabled": true to the fragment entry so the next merge flips live'senabled: false back on.
Onboarding a new Mac
chezmoi apply— installs the cask via brew-bundle, runs the
modify stub. On a fresh Mac the stub runs against an empty stdin,
so its output is the fragment re-serialized throughjson.dumps
with 2-space indent.- Approve the macOS prompt to set Yojam as the default browser.
- Yojam launches, imports the fragment, fills upstream defaults,
auto-discovers browsers/email clients, mirrors state into the
group container plist (see Schema Notes). - To opt a host out, add a host check in
home/.chezmoiignore
next to the existing cask-presence gate.
Bump the schema version
version is a desired-wins scalar like any other. After a Yojam
upgrade, both ends must reach the new schema before the nextchezmoi apply — a stale fragment silently downgrades live, and an
unmigrated live with a bumped fragment writes the new version onto an
old-shape file (which can confuse Yojam's migrator). Safe order:
- Read upstream
SettingsExport.init(from:)for the newversion
literal and any new keys whose default differs from what you want. - Launch the upgraded Yojam so it migrates live to N+1 in place.
- Bump the
versionliteral inhome/.chezmoiassets/yojam-config.json
to N+1 (and pull in step 1's new keys). chezmoi apply— both ends now at N+1, the merge is a no-op forversion.
Schema Notes
Source: SettingsStore.{export,import}JSON, Rule.swift, andBrowserEntry.swift in upstream Yojam (github.com/fluffypony/yojam).
Required vs default fields:
- Rule: only
id(UUID) is required. Every other field defaults
(enabled: true,priority: 100,matchType: .domain,pattern: "",targetBundleId: "",rewriteRules: [],isBuiltIn: false). - BrowserEntry: only
bundleIdentifieris required.iddefaults
to a freshUUID(),enabled: true,source: .autoDetected, etc. - Top-level scalars with notable upstream defaults:
version: 5,iCloudSync: false,verticalThreshold: 8,periodicRescanInterval: 1800,recentURLRetentionMinutes: 30,activationMode: always,defaultSelection: alwaysFirst,pickerLayout: auto,launchAtLogin: false. Defaults belong out of the fragment — exceptversion: 5,
pinned to anchor the bump workflow.
Two state surfaces: config.json is the import/export interface
(chezmoi-managed); the per-Mac ~/Library/Group Containers/group.org.yojam.shared/Library/Preferences/group.org.yojam.shared.plist
mirrors runtime state and is what Yojam falls back to when config.json
is missing — so deleting live and relaunching does NOT yield upstream
defaults, it yields the group plist's last state.
Per-Machine Concerns
learnedDomainPreferencesaccumulates per-Mac; keep out of fragment.- Machine-scoped rules (
Rule.machineScopeIdentifiers: [String]?):
nil/empty fires on every Mac; non-empty fires only on those IDs.
Validation
chezmoi diff "$HOME/Library/Application Support/Yojam/config.json"
chezmoi verify
chezmoi apply --dry-run --verbose --exclude=scriptsThe stub's semantic_equal short-circuit returns current_text
unchanged on a no-op merge, so chezmoi diff stays clean even if Yojam
reorders keys or shifts whitespace. After editing this skill, run the
parser/frontmatter check from the chezmoi-management skill →references/meta-skill-maintenance.md.
Related
- Cask install + profile gating: the
chezmoi-managementskill →references/packages-and-secrets.md. - The
modify_+ deep-merge pattern in general: thechezmoi-managementskill
→references/app-config.md, plushome/dot_codex/modify_private_config.toml.tmplandhome/dot_claude/modify_private_settings.json.tmplas JSON/TOML
precedents.