How user remappings become a running Kanata config.
RuleCollectionsManager gathers enabled rules and custom rules.
ConfigurationService writes the keypath.kbd file.
ConfigReloadCoordinator sends TCP reload to Kanata.
.kbd config file is the single artifact. Every rule, keymap, layer, and launcher binding is expressed as Kanata configuration. KeyPath never reads the config back — it regenerates from its own model every time.configuration (discriminated union) controlling its display style, a targetLayer, and an array of KeyMapping values. Uses a type-safe enum for style-specific data: .list, .singleKeyPicker, .homeRowMods, .tapHoldPicker, .layerPresetPicker.public struct RuleCollection: Identifiable, Codable { let id: UUID var name: String var isEnabled: Bool var mappings: [KeyMapping] var targetLayer: RuleCollectionLayer var configuration: RuleCollectionConfiguration }
[RuleCollection] and [CustomRule], manages the active keymap, detects conflicts between rules, and triggers config regeneration via its onRulesChanged callback. All config writes go through regenerateConfigFromCollections().@MainActor final class RuleCollectionsManager { var ruleCollections: [RuleCollection] var customRules: [CustomRule] var activeKeymapId: String // Single write path for all config changes var onRulesChanged: (() async -> Void)? }
keypath.kbd file from mappings, manages file watching for external edits, and provides the current KanataConfiguration. Preserves user-authored sections outside the KP:BEGIN/KP:END sentinel blocks.public final class ConfigurationService: FileConfigurationProviding { let configurationPath: String // ~/.config/keypath/keypath.kbd func current() async -> KanataConfiguration func reload() async throws -> KanataConfiguration func saveConfiguration(_: [RuleCollection], _: [CustomRule]) async throws }
@MainActor final class ConfigReloadCoordinator { func triggerConfigReload() async -> ReloadResult var onReloadSuccess: (() -> Void)? }
CustomRules.json.public struct CustomRule: Identifiable, Sendable { var input: String var output: String var behavior: MappingBehavior? // tap-hold, macro, etc. var targetLayer: RuleCollectionLayer var deviceOverrides: [DeviceKeyOverride]? }
RuleCollectionsManager updates the collection, fires onRulesChangedactiveKeymapId updated, full regeneration triggeredConfigurationService.saveConfiguration() writes keypath.kbdConfigReloadCoordinator.triggerConfigReload() tells Kanata to pick up the new config.kbd file is not enough. Without a TCP reload, Kanata runs stale config. Every write must be followed by triggerConfigReload().ConfigReloadCoordinator. It checks service health, permission gates, and handles rollback. Raw TCP calls bypass safety..kbd file → Kanata. Never reverse. JSON stores are the source of truth; the config file is a derived artifact regenerated on every change. See ADR-025.