diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f0ea4b..d44d25b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,6 @@ ## 1.0.0 - 2026-06-04 - Initial GitHub-ready release. -- Added real `/etc/hosts` apply and revert flow through a bundled privileged helper. -- Added managed-block replacement, backups, and DNS cache flushing. -- Added SwiftUI app shell with sidebar contexts, hosts editor, settings, menu bar access, import/export, and notarization scripts. +- DNS Proxy Network Extension for applying host mappings without editing system files. +- App Group shared configuration between main app and DNS proxy extension. +- SwiftUI app shell with sidebar contexts, hosts editor, settings, menu bar access, import/export, and notarization scripts. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 550bf9b..f372364 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -31,5 +31,4 @@ Run the app locally with: ## Safety -Locale edits `/etc/hosts`, so changes to `SystemApplyService` should be reviewed carefully. The app must preserve all content outside its managed block and keep creating backups before writes. - +Locale applies DNS mappings through a sandboxed DNS Proxy Network Extension. Changes to `SystemApplyService`, `DNSProxyController`, or `DNSProxyProvider` should be reviewed carefully. The app must not introduce direct `/etc/hosts` edits, privileged helpers, or `osascript` calls. diff --git a/Sources/LocaleApp/LocaleStore.swift b/Sources/LocaleApp/LocaleStore.swift index 33a3250..5094303 100644 --- a/Sources/LocaleApp/LocaleStore.swift +++ b/Sources/LocaleApp/LocaleStore.swift @@ -151,6 +151,11 @@ final class LocaleStore: ObservableObject { copy.isActive = false copy.lastActivated = nil copy.createdAt = Date() + copy.hosts = context.hosts.map { host in + var newHost = host + newHost.id = UUID() + return newHost + } if let idx = contexts.firstIndex(where: { $0.id == context.id }) { contexts.insert(copy, at: idx + 1) } else { diff --git a/Sources/LocaleApp/Models.swift b/Sources/LocaleApp/Models.swift index 8714654..c26e79c 100644 --- a/Sources/LocaleApp/Models.swift +++ b/Sources/LocaleApp/Models.swift @@ -68,8 +68,10 @@ struct NetworkContext: Identifiable, Codable, Equatable, Sendable { var activeHostCount: Int { hosts.filter(\.isEnabled).count } var disabledHostCount: Int { hosts.filter { !$0.isEnabled }.count } + static let homeContextID = UUID(uuidString: "00000000-0000-0000-0000-000000000001")! + static let homeContext = NetworkContext( - id: UUID(), + id: homeContextID, name: "Home", color: .green, isActive: true, diff --git a/Sources/LocaleApp/SidebarView.swift b/Sources/LocaleApp/SidebarView.swift index 8520bd7..fc50ba8 100644 --- a/Sources/LocaleApp/SidebarView.swift +++ b/Sources/LocaleApp/SidebarView.swift @@ -92,6 +92,8 @@ struct SidebarContextRow: View { @State private var isActivePulse = false + @EnvironmentObject var store: LocaleStore + var body: some View { Button(action: onSelect) { HStack(spacing: 9) { @@ -159,10 +161,11 @@ struct SidebarContextRow: View { if context.isActive { isActivePulse = true } } .contextMenu { - Button("Activate") {} - Button("Duplicate") {} + Button("Activate") { store.activateContext(context) } + Button("Duplicate") { store.duplicateContext(context) } Divider() - Button("Delete", role: .destructive) {} + Button("Delete", role: .destructive) { store.deleteContext(context) } + .disabled(context.id == NetworkContext.homeContextID) } } }