Restore recent accounts and return from native browser sign-in
Reviewed by GPT-6 Astra. Add saved account switching, app URL handlers, and domain-based Store bundle identifiers. Both Mac and iOS signed build 4 archives and exports verified under Team Tito.
10 files changed,
+176
−74
native/apple/Package.resolved+14 −14native/apple/Package.swift+2 −2native/apple/Sources/DocsApp/DocsAccountWindow.swift+19 −0native/apple/Sources/DocsApp/DocsApp.swift+1 −8native/apple/Sources/DocsKit/DocsModel.swift+11 −1native/apple/Sources/DocsKit/DocsWorkspace.swift+17 −5native/ios/Info.plist+35 −0native/ios/project.yml+43 −29native/macos/Info.plist+13 −0native/macos/project.yml+21 −15
CI failed — run details
diff --git a/native/apple/Package.resolved b/native/apple/Package.resolved| index 97226fe..b524238 100644 |
| --- a/native/apple/Package.resolved |
| +++ b/native/apple/Package.resolved |
| @@ -1,23 +1,23 @@ |
| { |
| - "originHash": "af87b24f930f4de462069543c80976a38f576581b91d71d7d80bb1ed34ea6d76", |
| - "pins": [ |
| + "originHash" : "9f8c968e09f32ff2986c7249d011e7c8981f1171a5abe4d00e8b4e6680ad2777", |
| + "pins" : [ |
| { |
| - "identity": "native", |
| - "kind": "remoteSourceControl", |
| - "location": "https://amadan.net/rastrillo/native", |
| - "state": { |
| - "revision": "4c61d6283a188c95647e1d0e609e69d29c2e7fa1" |
| + "identity" : "native", |
| + "kind" : "remoteSourceControl", |
| + "location" : "https://amadan.net/rastrillo/native", |
| + "state" : { |
| + "revision" : "229525909e1d6ac3b6a353d1c4d022798b0e9a24" |
| } |
| }, |
| { |
| - "identity": "sparkle", |
| - "kind": "remoteSourceControl", |
| - "location": "https://github.com/sparkle-project/Sparkle", |
| - "state": { |
| - "revision": "ac2def288cbff5cfc7df3ffef6abdf45b72bcb0a", |
| - "version": "2.9.6" |
| + "identity" : "sparkle", |
| + "kind" : "remoteSourceControl", |
| + "location" : "https://github.com/sparkle-project/Sparkle", |
| + "state" : { |
| + "revision" : "ac2def288cbff5cfc7df3ffef6abdf45b72bcb0a", |
| + "version" : "2.9.6" |
| } |
| } |
| ], |
| - "version": 3 |
| + "version" : 3 |
| } |
diff --git a/native/apple/Package.swift b/native/apple/Package.swift| index 43df1e0..ab1935f 100644 |
| --- a/native/apple/Package.swift |
| +++ b/native/apple/Package.swift |
| @@ -2,8 +2,8 @@ |
| import PackageDescription |
| import Foundation |
| -let sharedNative: Package.Dependency = ProcessInfo.processInfo.environment["OFICINA_NATIVE_PACKAGE_PATH"].map { .package(path: $0) } |
| - ?? .package(url: "https://amadan.net/rastrillo/native", revision: "4c61d6283a188c95647e1d0e609e69d29c2e7fa1") |
| +let sharedNative: Package.Dependency = ProcessInfo.processInfo.environment["OFICINA_NATIVE_PACKAGE_PATH"].map { .package(name: "native", path: $0) } |
| + ?? .package(url: "https://amadan.net/rastrillo/native", revision: "229525909e1d6ac3b6a353d1c4d022798b0e9a24") |
| let package = Package(name: "DocsNative", defaultLocalization: "en", platforms: [.macOS(.v26), .iOS(.v26)], |
| products: [.library(name: "DocsKit", targets: ["DocsKit"]), .executable(name: "DocsMac", targets: ["DocsMac"])], |
diff --git a/native/apple/Sources/DocsApp/DocsAccountWindow.swift b/native/apple/Sources/DocsApp/DocsAccountWindow.swift| new file mode 100644 |
| index 0000000..1be032d |
| --- /dev/null |
| +++ b/native/apple/Sources/DocsApp/DocsAccountWindow.swift |
| @@ -0,0 +1,19 @@ |
| +import RastrilloApple |
| +import DocsKit |
| +import SwiftUI |
| + |
| +/// Each window owns its navigation and model lifetime. Switching an account in |
| +/// one window must never discard another window's unconfirmed edits. |
| +struct DocsAccountWindow: View { |
| + @StateObject private var connection = NativeConnection(defaultServer: "https://docs.oficina.works") |
| + var body: some View { |
| + Group { |
| + if let client = connection.client { |
| + DocsWorkspace(client: client, accounts: connection.showAccounts, signOut: connection.signOut) |
| + .id(connection.activeAccountID) |
| + } else { |
| + NativeAccountsView("Oficina Docs", systemImage: "doc.text", connection: connection) |
| + } |
| + }.onOpenURL(perform: connection.handleOpenURL) |
| + } |
| +} |
diff --git a/native/apple/Sources/DocsApp/DocsApp.swift b/native/apple/Sources/DocsApp/DocsApp.swift| index b6f42cf..8f95341 100644 |
| --- a/native/apple/Sources/DocsApp/DocsApp.swift |
| +++ b/native/apple/Sources/DocsApp/DocsApp.swift |
| @@ -11,15 +11,8 @@ import SwiftUI |
| #if os(macOS) |
| @NSApplicationDelegateAdaptor(DocsApplicationDelegate.self) private var appDelegate |
| #endif |
| - @StateObject private var connection = NativeConnection(defaultServer: "https://docs.oficina.works") |
| var body: some Scene { |
| - WindowGroup { |
| - if let client = connection.client { |
| - DocsWorkspace(client: client, signOut: connection.signOut) |
| - } else { |
| - NativeSignInView("Oficina Docs", systemImage: "doc.text", connection: connection) |
| - } |
| - } |
| + WindowGroup { DocsAccountWindow() } |
| #if os(macOS) |
| .defaultSize(width: 1120, height: 820) |
| #if canImport(RastrilloMacUpdates) |
diff --git a/native/apple/Sources/DocsKit/DocsModel.swift b/native/apple/Sources/DocsKit/DocsModel.swift| index d1527bc..623479d 100644 |
| --- a/native/apple/Sources/DocsKit/DocsModel.swift |
| +++ b/native/apple/Sources/DocsKit/DocsModel.swift |
| @@ -14,6 +14,8 @@ import RastrilloApple |
| var inputPaused = false |
| var explicitCommandRevision: UInt64 = 0 |
| var loading = false |
| + var creatingDocument = false |
| + private var lifetime = UUID() |
| var selection = NSRange(location: 0, length: 0) |
| var typingMarks: [String: JSONValue]? |
| private var replica: CRDTReplica? |
| @@ -38,12 +40,19 @@ import RastrilloApple |
| } catch { errorKey = "docs.error.generic" } |
| } |
| func create(title: String) async { |
| + guard !creatingDocument, !hasUnsavedChanges else { return } |
| + let lifetime = self.lifetime |
| + creatingDocument = true |
| + defer { creatingDocument = false } |
| do { |
| var form = URLComponents(); form.queryItems = [URLQueryItem(name: "title", value: title)] |
| let (data, status) = try await client.response("/documents", method: "POST", body: Data((form.percentEncodedQuery ?? "").replacingOccurrences(of: "+", with: "%2B").utf8), contentType: "application/x-www-form-urlencoded") |
| guard status == 201 else { throw CRDTReplica.Failure.rejected } |
| let item = try JSONDecoder().decode(DocumentSummary.self, from: data) |
| - await load(); await open(item.sqid) |
| + guard self.lifetime == lifetime else { return } |
| + await load() |
| + guard self.lifetime == lifetime else { return } |
| + await open(item.sqid) |
| } catch { errorKey = "docs.error.generic" } |
| } |
| func open(_ sqid: String) async { |
| @@ -140,6 +149,7 @@ import RastrilloApple |
| } else { await flush() } |
| } |
| func stop() async { |
| + lifetime = UUID() |
| guard !hasUnsavedChanges else { return } |
| documentEpoch = UUID(); streamTask?.cancel(); streamTask = nil |
| let previous = replica; replica = nil; detail = nil; projection = nil |
diff --git a/native/apple/Sources/DocsKit/DocsWorkspace.swift b/native/apple/Sources/DocsKit/DocsWorkspace.swift| index fc412e7..64a63a1 100644 |
| --- a/native/apple/Sources/DocsKit/DocsWorkspace.swift |
| +++ b/native/apple/Sources/DocsKit/DocsWorkspace.swift |
| @@ -12,12 +12,12 @@ import SwiftUI |
| @State private var images = DocsImageStore() |
| @State private var column: NavigationSplitViewColumn = .sidebar |
| private let signOut: () -> Void |
| + private let accounts: (() -> Void)? |
| private let client: NativeHTTP |
| - public init(client: NativeHTTP, signOut: @escaping () -> Void) { |
| - _model = State(initialValue: DocsModel(client: client)); self.signOut = signOut; self.client = client |
| + public init(client: NativeHTTP, accounts: (() -> Void)? = nil, signOut: @escaping () -> Void) { |
| + _model = State(initialValue: DocsModel(client: client)); self.signOut = signOut; self.accounts = accounts; self.client = client |
| } |
| - public var body: some View { |
| - NavigationSplitView(preferredCompactColumn: $column) { |
| + private var library: some View { |
| List { |
| ForEach(model.documents) { document in |
| Button { Task { await model.open(document.sqid) } } label: { |
| @@ -29,10 +29,22 @@ import SwiftUI |
| .onSubmit(of: .search) { Task { await model.load(query: query) } } |
| .navigationTitle(Text("native.docs", bundle: DocsResources.bundle)) |
| .toolbar { |
| + if let accounts { |
| + ToolbarItem(placement: .navigation) { |
| + Button("Accounts", systemImage: "chevron.backward", action: accounts).disabled(model.hasUnsavedChanges || model.creatingDocument) |
| + } |
| + } |
| + |
| + ToolbarItemGroup { |
| DocsJumpControls(presented: $jumping) |
| if model.canCreate { Button { creating = true } label: { Label { Text("docs.list.create.label", bundle: DocsResources.bundle) } icon: { Image(systemName: "plus") } }.accessibilityIdentifier("docs.create") } |
| - Button { if !model.hasUnsavedChanges { signOut() } } label: { Text("docs.signout.label", bundle: DocsResources.bundle) }.disabled(model.hasUnsavedChanges) |
| + Button { if !model.hasUnsavedChanges && !model.creatingDocument { signOut() } } label: { Text("docs.signout.label", bundle: DocsResources.bundle) }.disabled(model.hasUnsavedChanges || model.creatingDocument) |
| + } |
| } |
| + } |
| + public var body: some View { |
| + NavigationSplitView(preferredCompactColumn: $column) { |
| + library |
| } detail: { |
| VStack(spacing: 0) { |
| if let key = model.errorKey { |
diff --git a/native/ios/Info.plist b/native/ios/Info.plist| new file mode 100644 |
| index 0000000..bdaf0f0 |
| --- /dev/null |
| +++ b/native/ios/Info.plist |
| @@ -0,0 +1,35 @@ |
| +<?xml version="1.0" encoding="UTF-8"?> |
| +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
| +<plist version="1.0"> |
| +<dict> |
| + <key>CFBundleDevelopmentRegion</key> |
| + <string>$(DEVELOPMENT_LANGUAGE)</string> |
| + <key>CFBundleExecutable</key> |
| + <string>$(EXECUTABLE_NAME)</string> |
| + <key>CFBundleIdentifier</key> |
| + <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> |
| + <key>CFBundleInfoDictionaryVersion</key> |
| + <string>6.0</string> |
| + <key>CFBundleName</key> |
| + <string>$(PRODUCT_NAME)</string> |
| + <key>CFBundlePackageType</key> |
| + <string>APPL</string> |
| + <key>CFBundleShortVersionString</key> |
| + <string>$(MARKETING_VERSION)</string> |
| + <key>CFBundleURLTypes</key> |
| + <array> |
| + <dict> |
| + <key>CFBundleTypeRole</key> |
| + <string>Editor</string> |
| + <key>CFBundleURLName</key> |
| + <string>works.oficina.docs.signin</string> |
| + <key>CFBundleURLSchemes</key> |
| + <array> |
| + <string>works.oficina.docs</string> |
| + </array> |
| + </dict> |
| + </array> |
| + <key>CFBundleVersion</key> |
| + <string>$(CURRENT_PROJECT_VERSION)</string> |
| +</dict> |
| +</plist> |
diff --git a/native/ios/project.yml b/native/ios/project.yml| index 295726b..9bd5386 100644 |
| --- a/native/ios/project.yml |
| +++ b/native/ios/project.yml |
| @@ -8,61 +8,74 @@ packages: |
| path: ../apple |
| RastrilloNative: |
| url: https://amadan.net/rastrillo/native |
| - revision: 4c61d6283a188c95647e1d0e609e69d29c2e7fa1 |
| + revision: 229525909e1d6ac3b6a353d1c4d022798b0e9a24 |
| settings: |
| base: |
| SWIFT_VERSION: '6.0' |
| CODE_SIGN_STYLE: Automatic |
| - MARKETING_VERSION: '0.1.0' |
| + MARKETING_VERSION: 0.1.0 |
| CURRENT_PROJECT_VERSION: '1' |
| targets: |
| DocsPhone: |
| type: application |
| platform: iOS |
| sources: |
| - - ../apple/Sources/DocsApp |
| - - Assets.xcassets |
| - - path: ../LICENSE.lucide |
| - buildPhase: resources |
| - - path: ../PrivacyInfo.xcprivacy |
| - buildPhase: resources |
| + - ../apple/Sources/DocsApp |
| + - Assets.xcassets |
| + - path: ../LICENSE.lucide |
| + buildPhase: resources |
| + - path: ../PrivacyInfo.xcprivacy |
| + buildPhase: resources |
| dependencies: |
| - - package: DocsNative |
| - product: DocsKit |
| - - package: RastrilloNative |
| - product: RastrilloApple |
| + - package: DocsNative |
| + product: DocsKit |
| + - package: RastrilloNative |
| + product: RastrilloApple |
| settings: |
| base: |
| ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon |
| - PRODUCT_BUNDLE_IDENTIFIER: net.amadan.oficina.docs.ios |
| - TARGETED_DEVICE_FAMILY: '1,2' |
| - GENERATE_INFOPLIST_FILE: YES |
| + PROVISIONING_PROFILE_SPECIFIER: $(OFICINA_PROVISIONING_PROFILE) |
| + PRODUCT_BUNDLE_IDENTIFIER: works.oficina.docs |
| + TARGETED_DEVICE_FAMILY: 1,2 |
| + GENERATE_INFOPLIST_FILE: true |
| INFOPLIST_KEY_CFBundleDisplayName: Oficina Docs |
| - INFOPLIST_KEY_UILaunchScreen_Generation: YES |
| + INFOPLIST_KEY_UILaunchScreen_Generation: true |
| + info: |
| + path: Info.plist |
| + properties: |
| + CFBundleURLTypes: |
| + - CFBundleURLName: works.oficina.docs.signin |
| + CFBundleURLSchemes: |
| + - works.oficina.docs |
| + CFBundleTypeRole: Editor |
| + CFBundleShortVersionString: $(MARKETING_VERSION) |
| + CFBundleVersion: $(CURRENT_PROJECT_VERSION) |
| DocsReviewPhone: |
| type: application |
| platform: iOS |
| - sources: [Review] |
| + sources: |
| + - Review |
| dependencies: |
| - - package: DocsNative |
| - product: DocsKit |
| - - package: RastrilloNative |
| - product: RastrilloApple |
| + - package: DocsNative |
| + product: DocsKit |
| + - package: RastrilloNative |
| + product: RastrilloApple |
| settings: |
| base: |
| PRODUCT_BUNDLE_IDENTIFIER: net.amadan.oficina.docs.review |
| - TARGETED_DEVICE_FAMILY: '1,2' |
| - GENERATE_INFOPLIST_FILE: YES |
| - INFOPLIST_KEY_UILaunchScreen_Generation: YES |
| + TARGETED_DEVICE_FAMILY: 1,2 |
| + GENERATE_INFOPLIST_FILE: true |
| + INFOPLIST_KEY_UILaunchScreen_Generation: true |
| DocsPhoneUITests: |
| type: bundle.ui-testing |
| platform: iOS |
| - sources: [UITests] |
| + sources: |
| + - UITests |
| dependencies: |
| - - target: DocsReviewPhone |
| + - target: DocsReviewPhone |
| settings: |
| base: |
| - GENERATE_INFOPLIST_FILE: YES |
| + GENERATE_INFOPLIST_FILE: true |
| TEST_TARGET_NAME: DocsReviewPhone |
| schemes: |
| DocsReviewPhone: |
| @@ -70,6 +83,7 @@ schemes: |
| targets: |
| DocsReviewPhone: all |
| test: |
| - targets: [DocsPhoneUITests] |
| + targets: |
| + - DocsPhoneUITests |
| environmentVariables: |
| - DOCS_NATIVE_REVIEW_INPUT: "$(DOCS_NATIVE_REVIEW_INPUT)" |
| + DOCS_NATIVE_REVIEW_INPUT: $(DOCS_NATIVE_REVIEW_INPUT) |
diff --git a/native/macos/Info.plist b/native/macos/Info.plist| index 7a59688..b787815 100644 |
| --- a/native/macos/Info.plist |
| +++ b/native/macos/Info.plist |
| @@ -20,6 +20,19 @@ |
| <string>APPL</string> |
| <key>CFBundleShortVersionString</key> |
| <string>$(MARKETING_VERSION)</string> |
| + <key>CFBundleURLTypes</key> |
| + <array> |
| + <dict> |
| + <key>CFBundleTypeRole</key> |
| + <string>Editor</string> |
| + <key>CFBundleURLName</key> |
| + <string>works.oficina.docs.signin</string> |
| + <key>CFBundleURLSchemes</key> |
| + <array> |
| + <string>works.oficina.docs</string> |
| + </array> |
| + </dict> |
| + </array> |
| <key>CFBundleVersion</key> |
| <string>$(CURRENT_PROJECT_VERSION)</string> |
| <key>LSApplicationCategoryType</key> |
diff --git a/native/macos/project.yml b/native/macos/project.yml| index e0a58f1..c2e6d94 100644 |
| --- a/native/macos/project.yml |
| +++ b/native/macos/project.yml |
| @@ -7,31 +7,31 @@ packages: |
| path: ../apple |
| RastrilloNative: |
| url: https://amadan.net/rastrillo/native |
| - revision: 4c61d6283a188c95647e1d0e609e69d29c2e7fa1 |
| + revision: 229525909e1d6ac3b6a353d1c4d022798b0e9a24 |
| settings: |
| base: |
| SWIFT_VERSION: '6.0' |
| CODE_SIGN_STYLE: Automatic |
| - MARKETING_VERSION: '0.1.0' |
| + MARKETING_VERSION: 0.1.0 |
| CURRENT_PROJECT_VERSION: '3' |
| - ENABLE_HARDENED_RUNTIME: YES |
| + ENABLE_HARDENED_RUNTIME: true |
| targets: |
| DocsMacStore: |
| type: application |
| platform: macOS |
| sources: |
| - - ../apple/Sources/DocsApp |
| - - path: AppIcon.icns |
| - buildPhase: resources |
| - - path: ../LICENSE.lucide |
| - buildPhase: resources |
| - - path: ../PrivacyInfo.xcprivacy |
| - buildPhase: resources |
| + - ../apple/Sources/DocsApp |
| + - path: AppIcon.icns |
| + buildPhase: resources |
| + - path: ../LICENSE.lucide |
| + buildPhase: resources |
| + - path: ../PrivacyInfo.xcprivacy |
| + buildPhase: resources |
| dependencies: |
| - - package: DocsNative |
| - product: DocsKit |
| - - package: RastrilloNative |
| - product: RastrilloApple |
| + - package: DocsNative |
| + product: DocsKit |
| + - package: RastrilloNative |
| + product: RastrilloApple |
| info: |
| path: Info.plist |
| properties: |
| @@ -42,7 +42,13 @@ targets: |
| CFBundleIconFile: AppIcon |
| LSApplicationCategoryType: public.app-category.productivity |
| NSHighResolutionCapable: true |
| + CFBundleURLTypes: |
| + - CFBundleURLName: works.oficina.docs.signin |
| + CFBundleURLSchemes: |
| + - works.oficina.docs |
| + CFBundleTypeRole: Editor |
| settings: |
| base: |
| - PRODUCT_BUNDLE_IDENTIFIER: net.amadan.oficina.docs |
| + PROVISIONING_PROFILE_SPECIFIER: $(OFICINA_PROVISIONING_PROFILE) |
| + PRODUCT_BUNDLE_IDENTIFIER: works.oficina.docs |
| CODE_SIGN_ENTITLEMENTS: AppStore.entitlements |