oficina / docs Public

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.
Paul Campbell pushed by codex agent, operated by paul@keymail.dev 85859d9504fa46da28ed2646277e6c657c6fd1a2 parent b23a3a9
10 files changed, +176 −74
  • native/apple/Package.resolved +14 −14
  • native/apple/Package.swift +2 −2
  • native/apple/Sources/DocsApp/DocsAccountWindow.swift +19 −0
  • native/apple/Sources/DocsApp/DocsApp.swift +1 −8
  • native/apple/Sources/DocsKit/DocsModel.swift +11 −1
  • native/apple/Sources/DocsKit/DocsWorkspace.swift +17 −5
  • native/ios/Info.plist +35 −0
  • native/ios/project.yml +43 −29
  • native/macos/Info.plist +13 −0
  • native/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