Sync Home with pull then push and prepare 1.0.1 build 8 (#124)
Dependency security audit / rustsec (push) Successful in 20s
Tagged release / prepare-release (push) Successful in 4s
Tagged release / build-unix (push) Successful in 1h15m17s
Tagged release / build-windows (push) Successful in 19m26s
Tagged release / publish-release (push) Successful in 3s

This commit is contained in:
hugo committed 2026-09-12 12:53:30 +02:00
1 parent 3c3574b449
commit aec3f237a9
12 files changed
+1174 -50

No files matched your search

+3 -3
View File
@@ -15,11 +15,11 @@ copyright = "2026 rfc1437"
review_notes = "IronStorage works entirely on-device without an account or remote service. On first launch, generate a local GPG key and choose a passphrase. You can then create folders without unlocking, and create encrypted password or TOTP entries after entering that passphrase. Git history is initialized locally; HTTPS synchronization is optional and is not needed for review. Entries appear on Apple Watch only after selecting them for Watch sharing on iPhone."
[release]
version = "1.0"
build = "7"
version = "1.0.1"
build = "8"
minimum_ios = "17.0"
minimum_watchos = "26.0"
notes = "Initial iPhone and Apple Watch release with local GPG key generation, pass-compatible local Git history, optional HTTPS synchronization, password folders, and explicitly shared TOTP codes on Apple Watch."
notes = "Pull down on Home to pull remote changes and then push local commits. Pull failures stop synchronization and are shown immediately. Includes OTP QR import improvements and the Apple Watch companion."
[[screenshots]]
file = "apple/AppStore/screenshots/iphone-6.5/home.png"
+14
View File
@@ -1254,6 +1254,8 @@ public protocol MobileHomeOperationProtocol: AnyObject, Sendable {
func refreshIfStale() throws -> MobileHomePage
func sync() throws -> MobileHomePage
}
open class MobileHomeOperation: MobileHomeOperationProtocol, @unchecked Sendable {
fileprivate let handle: UInt64
@@ -1380,6 +1382,15 @@ open func refreshIfStale()throws -> MobileHomePage {
})
}
open func sync()throws -> MobileHomePage {
return try FfiConverterTypeMobileHomePage_lift(try rustCallWithError(FfiConverterTypeMobileHomeFfiError_lift) {
uniffiCallStatus in
uniffi_ironstorage_apple_fn_method_mobilehomeoperation_sync(
self.uniffiCloneHandle(),uniffiCallStatus
)
})
}
}
@@ -8120,6 +8131,9 @@ private let initializationResult: InitializationResult = {
if (uniffi_ironstorage_apple_checksum_method_mobilehomeoperation_refresh_if_stale() != 27818) {
return InitializationResult.apiChecksumMismatch
}
if (uniffi_ironstorage_apple_checksum_method_mobilehomeoperation_sync() != 48214) {
return InitializationResult.apiChecksumMismatch
}
if (uniffi_ironstorage_apple_checksum_method_mobilekeytransfer_export() != 50467) {
return InitializationResult.apiChecksumMismatch
}
+11
View File
@@ -518,6 +518,11 @@ RustBuffer uniffi_ironstorage_apple_fn_method_mobilehomeoperation_push(uint64_t
RustBuffer uniffi_ironstorage_apple_fn_method_mobilehomeoperation_refresh_if_stale(uint64_t ptr, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEHOMEOPERATION_SYNC
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEHOMEOPERATION_SYNC
RustBuffer uniffi_ironstorage_apple_fn_method_mobilehomeoperation_sync(uint64_t ptr, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_CLONE_MOBILEKEYTRANSFER
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_CLONE_MOBILEKEYTRANSFER
uint64_t uniffi_ironstorage_apple_fn_clone_mobilekeytransfer(uint64_t handle, RustCallStatus *_Nonnull out_status
@@ -1363,6 +1368,12 @@ uint16_t uniffi_ironstorage_apple_checksum_method_mobilehomeoperation_push(void
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEHOMEOPERATION_REFRESH_IF_STALE
uint16_t uniffi_ironstorage_apple_checksum_method_mobilehomeoperation_refresh_if_stale(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEHOMEOPERATION_SYNC
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEHOMEOPERATION_SYNC
uint16_t uniffi_ironstorage_apple_checksum_method_mobilehomeoperation_sync(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEKEYTRANSFER_EXPORT
File diff suppressed because it is too large. Load diff
+11 -3
View File
@@ -388,9 +388,11 @@ private final class ShellViewController: UITableViewController, MobileTabRoot {
case fetch
case pull
case push
case sync
var changesLocalStore: Bool {
if case .pull = self { return true }
if case .sync = self { return true }
return false
}
}
@@ -577,7 +579,7 @@ private final class ShellViewController: UITableViewController, MobileTabRoot {
refreshControl?.endRefreshing()
return
}
runHome(.pull)
runHome(.sync)
} else {
reloadShell()
}
@@ -750,7 +752,7 @@ private final class ShellViewController: UITableViewController, MobileTabRoot {
let refreshed = page.refreshedAt.map(formattedDate)
switch page.freshness {
case .neverRefreshed:
return "Not refreshed. Pull down to update the local clone or tap Refresh Status."
return "Not refreshed. Pull down to pull, then push your changes, or tap Refresh Status."
case .cached:
return "Cached · Last refreshed \(refreshed ?? "at an unknown time"). This is not live status."
case .current:
@@ -822,6 +824,7 @@ private final class ShellViewController: UITableViewController, MobileTabRoot {
case .fetch: try operation.fetch()
case .pull: try operation.pull()
case .push: try operation.push()
case .sync: try operation.sync()
}
return Result<MobileHomePage, HomeFailure>.success(page)
} catch let error as MobileHomeFfiError {
@@ -865,11 +868,16 @@ private final class ShellViewController: UITableViewController, MobileTabRoot {
UIAccessibility.post(notification: .announcement, argument: notice.title)
}
case let .failure(failure):
if request == .sync {
// Pull may have updated entries before a later push failed.
NotificationCenter.default.post(name: .ironStorageLocalStoreDidChange, object: nil)
NotificationCenter.default.post(name: .ironStorageWatchSnapshotDidChange, object: nil)
}
tableView.reloadData()
if homePage == nil {
showHomeFailure(failure)
presentHomeFailure(failure, retry: request ?? .fetch)
} else if failure.kind == .interrupted {
} else if failure.kind == .interrupted, request != .sync {
UIAccessibility.post(notification: .announcement, argument: failure.title)
} else {
presentHomeFailure(failure, retry: request ?? .fetch)
+7 -1
View File
@@ -9,7 +9,7 @@ actually exercised.
| Area | Implemented boundary | Automated evidence | Manual release evidence |
| --- | --- | --- | --- |
| Home tab, local history, and optional remote activity | UIKit renders Rust `MobileHomePage` and typed Commit/Fetch/Pull/Push actions | `mobile_home` unit tests, `git_embedded`, and `crates/apple` bridge tests | Verify a clean local store first, then attach a disposable HTTPS remote and exercise refresh and all four actions, including expired application-token recovery |
| Home tab, local history, and optional remote activity | UIKit renders Rust `MobileHomePage`; pulling down calls Rust sync (pull, then push only on success). Explicit Commit/Fetch/Pull/Push actions remain available. | `mobile_home` unit tests, `git_embedded` move/back and failed-pull regressions, and `crates/apple` bridge tests | Verify refresh and all four actions, including expired application-token recovery. Pull failures must show an error and prevent push. |
| Passwords tab, navigation, view, and edit | Rust supplies typed paths, fields, drafts, revisions, and mutations; UIKit presents them | `mobile_passwords`, `entry_documents`, `mobile_mutation`, and `mobile_authentication` tests | Open nested entries, edit/save/cancel, and confirm stale-edit recovery |
| Swipe move/copy/delete | Rust plans destinations, collisions, dirty-editor handling, and commits; UIKit also exposes accessibility actions | `plans_destinations_collisions_hidden_paths_and_stale_revisions` and authentication serialization tests | Exercise swipe and VoiceOver alternatives; confirm destructive prompts |
| TOTP tab and sharing | Rust discovers, caches, parses, selects, and generates OTP data | `mobile_totp`, `otp`, and `mobile_watch` integration tests | Discover entries, read a code, change Watch selection, and verify countdown |
@@ -88,6 +88,12 @@ the tester is ready to enter the passphrase again.
## Manual evidence still required
Issue #124 / version 1.0.1 build 8: iPhone and Watch simulator builds passed;
the existing iPhone simulator installation was updated in place and Home opened.
Automated Git tests cover move-and-move-back commits, network/authentication
failures, and merge conflicts without a subsequent push. Live account sync is
deferred to the user's TestFlight validation; no physical iPhone was updated.
Issue #120 was exercised on 2026-09-12 using the existing iPhone 17 Pro / iOS
26.5 simulator (`FB5C6929-C10D-40C8-9A94-5CC592037FDB`) and its simulated store.
TOTP Add imported a synthetic QR into `otp/Simulator Panel OTP`; the normal
+2 -2
View File
@@ -6,8 +6,8 @@ settings:
SWIFT_VERSION: "5.0"
CODE_SIGN_STYLE: Automatic
DEVELOPMENT_TEAM: MU22FMRGK8
MARKETING_VERSION: "1.0"
CURRENT_PROJECT_VERSION: "7"
MARKETING_VERSION: "1.0.1"
CURRENT_PROJECT_VERSION: "8"
targets:
IronStorage:
type: application