From 9f2dcedb1a4764f01f969b76402dfa5ab3ebc89a Mon Sep 17 00:00:00 2001 From: Rod Bahmanyari Date: Tue, 25 Aug 2026 17:54:09 -0700 Subject: [PATCH] Review follow-ups: globe deep-link, fil translation, pane-routing test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three items from review feedback on the gear PR: - The gateway globe's accessibility hint promises "opens settings", but its action presented the sheet on the sticky pane. It now passes .settings like the gear. - fil had English "settings" marked translated (Chessing234's catch); now "mga setting", matching the catalog's own fil precedent in content.accessibility.gateway_settings_hint. Also applied to app_info.tab.settings — the label of the very tab both controls deep-link to — so fil users don't hear "mga setting" and land on a tab labeled "settings". (content.alert.bluetooth_required.settings names the iOS Settings app and is left for a separate pass.) - A regression test pins the routing contract: a promised pane overwrites the sticky appInfo.selectedPane default, a plain present never touches it, and first-open leaves the key absent so new users still land on Info. Co-Authored-By: Claude Fable 5 --- bitchat/Localizable.xcstrings | 4 +-- bitchat/Views/ContentHeaderView.swift | 2 +- bitchatTests/AppArchitectureTests.swift | 43 +++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 3 deletions(-) diff --git a/bitchat/Localizable.xcstrings b/bitchat/Localizable.xcstrings index ae8335a5..739c9d8e 100644 --- a/bitchat/Localizable.xcstrings +++ b/bitchat/Localizable.xcstrings @@ -32410,7 +32410,7 @@ "fil" : { "stringUnit" : { "state" : "translated", - "value" : "settings" + "value" : "mga setting" } }, "fr" : { @@ -56149,7 +56149,7 @@ "en" : { "stringUnit" : { "state" : "translated", "value" : "settings" } }, "es" : { "stringUnit" : { "state" : "translated", "value" : "ajustes" } }, "fa" : { "stringUnit" : { "state" : "translated", "value" : "تنظیمات" } }, - "fil" : { "stringUnit" : { "state" : "translated", "value" : "settings" } }, + "fil" : { "stringUnit" : { "state" : "translated", "value" : "mga setting" } }, "fr" : { "stringUnit" : { "state" : "translated", "value" : "réglages" } }, "he" : { "stringUnit" : { "state" : "translated", "value" : "הגדרות" } }, "hi" : { "stringUnit" : { "state" : "translated", "value" : "सेटिंग्स" } }, diff --git a/bitchat/Views/ContentHeaderView.swift b/bitchat/Views/ContentHeaderView.swift index 1e465be7..6988c9b3 100644 --- a/bitchat/Views/ContentHeaderView.swift +++ b/bitchat/Views/ContentHeaderView.swift @@ -129,7 +129,7 @@ struct ContentHeaderView: View { if locationChannelsModel.gatewayEnabled { // The gateway toggle lives in the App Info settings pane // now, so the indicator deep-links there. - Button(action: { appChromeModel.presentAppInfo() }) { + Button(action: { appChromeModel.presentAppInfo(pane: .settings) }) { Image(systemName: "globe") .font(.bitchatSystem(size: 12)) .foregroundColor(palette.secondary.opacity(0.8)) diff --git a/bitchatTests/AppArchitectureTests.swift b/bitchatTests/AppArchitectureTests.swift index 747e737b..222794e8 100644 --- a/bitchatTests/AppArchitectureTests.swift +++ b/bitchatTests/AppArchitectureTests.swift @@ -462,6 +462,49 @@ struct AppArchitectureTests { #expect(chromeModel.showingFingerprintFor == nil) } + @Test("App Info pane routing: a promised pane overrides the sticky memory, a plain present never touches it") + @MainActor + func presentAppInfoPaneRouting() { + let viewModel = makeArchitectureViewModel() + let privateInboxModel = PrivateInboxModel(conversations: ConversationStore()) + let chromeModel = AppChromeModel(chatViewModel: viewModel, privateInboxModel: privateInboxModel) + + // The sticky pane lives in standard defaults (@AppStorage's store); + // restore whatever was there so this test can't leak state. + let key = "appInfo.selectedPane" + let defaults = UserDefaults.standard + let original = defaults.string(forKey: key) + defer { + if let original { + defaults.set(original, forKey: key) + } else { + defaults.removeObject(forKey: key) + } + } + + // First-ever open: the logo tap passes no pane, and the absent key is + // what lands new users on the gentler Info default — a plain present + // must not create it. + defaults.removeObject(forKey: key) + chromeModel.presentAppInfo() + #expect(defaults.string(forKey: key) == nil) + #expect(chromeModel.isAppInfoPresented) + + // Later opens: the logo tap keeps whatever pane the sheet was left on. + chromeModel.isAppInfoPresented = false + defaults.set(AppInfoView.Pane.info.rawValue, forKey: key) + chromeModel.presentAppInfo() + #expect(defaults.string(forKey: key) == AppInfoView.Pane.info.rawValue) + #expect(chromeModel.isAppInfoPresented) + + // Controls that promise a pane (the header gear and the gateway globe + // both say "settings") must land there even against sticky memory. + chromeModel.isAppInfoPresented = false + chromeModel.presentAppInfo(pane: .settings) + #expect(defaults.string(forKey: key) == AppInfoView.Pane.settings.rawValue) + #expect(chromeModel.isAppInfoPresented) + } + @Test("Triple-tap panic entry point only raises the confirmation dialog") @MainActor func requestPanicWipeAsksBeforeDestroying() {