field_ios

In-the-field app for Radroots on iOS
git clone https://radroots.dev/git/field_ios.git
Log | Files | Refs | README | LICENSE

commit 3358dbb65acabf0aa63b9090c60ece1bd5bbc947
parent 872a7906956353d86e400848d6ea0a6631dc829f
Author: triesap <tyson@radroots.org>
Date:   Thu,  1 Oct 2026 17:52:27 +0000

ios: clarify public publication and location consent

- Explain public posts and separately hosted public photos
- Label deliberately entered location as public information
- Qualify metadata stripping and exact saved location recovery
- Reduce Add view size debt without changing thresholds

Diffstat:
MPackage.swift | 1+
MTera.xcodeproj/project.pbxproj | 4++++
MTera/Views/TeraAddView.swift | 7+++++--
ATera/Views/TeraPublicationDisclosure.swift | 15+++++++++++++++
MTeraTests/TeraOfflineMediaTests.swift | 18+++++++++++-------
ATeraTests/TeraPublicDisclosureTests.swift | 92+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mproject.yml | 1+
Mrelease/provenance.json | 2+-
Mtest-fixtures/swiftlint-maintainability-baseline.v1.json | 2+-
9 files changed, 131 insertions(+), 11 deletions(-)

diff --git a/Package.swift b/Package.swift @@ -195,6 +195,7 @@ let package = Package( "Views/TeraSubmissionViews.swift", "Views/TeraPublicationTargetsView.swift", "Views/TeraAddView.swift", + "Views/TeraPublicationDisclosure.swift", "Views/TeraAddSaveStatus.swift", "Views/TeraDraftsSheet.swift", "Views/TeraEditingProtectionActions.swift", diff --git a/Tera.xcodeproj/project.pbxproj b/Tera.xcodeproj/project.pbxproj @@ -107,6 +107,7 @@ B4AE86BE0A9F7FBE0DEBD9AC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = ADFD21D28C5F9E58B70E76CA /* Localizable.strings */; }; B64A836C5E84924D28CB5D47 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AB8A30A75EFD65EFD938D248 /* Assets.xcassets */; }; B8A8E0FDD4FBC43AE8A9A1C4 /* TeraRecoverySchedulingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9970FE6EFEA7E9601A1F5B62 /* TeraRecoverySchedulingTests.swift */; }; + B9E397E21B1654C108AF74BF /* TeraPublicDisclosureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDAE66E2F9C3076F5770BEDE /* TeraPublicDisclosureTests.swift */; }; BB71614D2A6017C071A39BED /* TeraMutationAdmissionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02F322CF98F8BEB908B93215 /* TeraMutationAdmissionTests.swift */; }; BF0BF2D7F46217C4EBCA570C /* TeraCalendarPresentationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFC5639BDCD2A5C1C27FE38 /* TeraCalendarPresentationTests.swift */; }; C0332D7E91580922DED3597C /* TeraNativeRecoveryScheduleTestStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEC40593AB568A682B00D3B /* TeraNativeRecoveryScheduleTestStorage.swift */; }; @@ -302,6 +303,7 @@ FA1A8EA600F6BDC45333520C /* TeraCalendarComposerFFITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeraCalendarComposerFFITests.swift; sourceTree = "<group>"; }; FB77C087F15C2F42CD959607 /* TeraRuntimeClientTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeraRuntimeClientTests.swift; sourceTree = "<group>"; }; FD65DE234C7DD80CE3CB91EB /* TeraPhotoQualificationInput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeraPhotoQualificationInput.swift; sourceTree = "<group>"; }; + FDAE66E2F9C3076F5770BEDE /* TeraPublicDisclosureTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeraPublicDisclosureTests.swift; sourceTree = "<group>"; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -431,6 +433,7 @@ 56F7B42264CD896F76189F33 /* TeraPublicationRetryTests.swift */, 635EB4A552C563E85557A485 /* TeraPublicationStageTests.swift */, 293D968D4E2ACA3BA414BF8D /* TeraPublicationTargetTests.swift */, + FDAE66E2F9C3076F5770BEDE /* TeraPublicDisclosureTests.swift */, 156F07F1F2961FDF6539099A /* TeraRecoveryCompletionTests.swift */, BB4F87BA96C92630FE795A9D /* TeraRecoveryContinuationTests.swift */, 77F0FDC0D59BFCB5D7F928D5 /* TeraRecoveryInventoryTests.swift */, @@ -755,6 +758,7 @@ 6FED2D2255CC702B33CDC760 /* TeraOpenedMediaTests.swift in Sources */, 026981088C27CB79A49D9833 /* TeraOperationRecoveryTests.swift in Sources */, C46F8EB5B196FFDD37E0279A /* TeraProductStartupTests.swift in Sources */, + B9E397E21B1654C108AF74BF /* TeraPublicDisclosureTests.swift in Sources */, 093A6AC6C9B724E465507004 /* TeraPublicationEvidenceTests.swift in Sources */, 7D220F7F83A80A2D312AA5B0 /* TeraPublicationNavigationTests.swift in Sources */, AA8B25877C9A3BD331B1B1AE /* TeraPublicationRetryTests.swift in Sources */, diff --git a/Tera/Views/TeraAddView.swift b/Tera/Views/TeraAddView.swift @@ -45,6 +45,7 @@ struct TeraAddView: View { .foregroundStyle(.secondary) } } else { + TeraPublicationDisclosure() Group { Section("What are you sharing?") { Picker("Type", selection: typeBinding) { @@ -142,7 +143,9 @@ struct TeraAddView: View { } .task { await store.start() } } +} +extension TeraAddView { @ViewBuilder private var composerFields: some View { switch store.form.commandType { @@ -167,7 +170,7 @@ struct TeraAddView: View { ) contentEditor(prompt: "Event details (optional)") formTextField( - label: "Location (optional)", text: optional(\.location), focus: .location, + label: "Public location (optional)", text: optional(\.location), focus: .location, identifier: "radroots.add.location" ) TeraCalendarComposerFields(store: store) @@ -187,7 +190,7 @@ struct TeraAddView: View { ) contentEditor(prompt: "Details") formTextField( - label: "Location", text: optional(\.location), focus: .location, + label: "Public location", text: optional(\.location), focus: .location, identifier: "radroots.add.location" ) } diff --git a/Tera/Views/TeraPublicationDisclosure.swift b/Tera/Views/TeraPublicationDisclosure.swift @@ -0,0 +1,15 @@ +import SwiftUI + +struct TeraPublicationDisclosure: View { + var body: some View { + Section("Public publication") { + Text("Submit publishes to public Nostr relays. A local network helps you discover nearby posts; it is not a private group.") + .accessibilityIdentifier("tera.add.disclosure.public") + Text("Photos are uploaded to a separate public photo service. Anyone who obtains their links may view or copy them. Prepared photos have embedded location and camera metadata removed, but visible details can still identify people and places.") + .accessibilityIdentifier("tera.add.disclosure.media") + Text("Location text you enter is public when you submit. Include an exact address only if you intend to share it publicly. Tera does not request your device location; you can enter a less precise description or leave optional location blank.") + .accessibilityIdentifier("tera.add.disclosure.location") + Text("Save draft keeps editing on this device. Submission and retraction cannot guarantee that remote copies will be removed.") + } + } +} diff --git a/TeraTests/TeraOfflineMediaTests.swift b/TeraTests/TeraOfflineMediaTests.swift @@ -184,19 +184,19 @@ struct OfflineMediaFixture { let picker: OfflineMediaPicker let transfer = BackgroundTransferHarness() - init() throws { + init(image: Data? = nil, filename: String = "input.png", mediaType: String = "image/png") throws { runtime = try MediaOwnershipFixture() roots = try TeraDurableMediaRoots.selectingStaging(in: RadrootsAppleFileRoots(appIdentifier: "test.offline-media", dataRoot: runtime.root.appendingPathComponent("media/data"), cacheRoot: runtime.root.appendingPathComponent("media/cache"), temporaryRoot: runtime.root.appendingPathComponent("media/temporary"))) try FileManager.default.createDirectory(at: roots.cacheRoot, withIntermediateDirectories: true) - let bytes = UIGraphicsImageRenderer(size: CGSize(width: 2, height: 2)).pngData { context in + let bytes = image ?? UIGraphicsImageRenderer(size: CGSize(width: 2, height: 2)).pngData { context in UIColor.green.setFill() context.fill(CGRect(x: 0, y: 0, width: 2, height: 2)) } - try bytes.write(to: roots.cacheRoot.appendingPathComponent("input.png")) - picker = OfflineMediaPicker(byteSize: UInt64(bytes.count)) + try bytes.write(to: roots.cacheRoot.appendingPathComponent(filename)) + picker = OfflineMediaPicker(byteSize: UInt64(bytes.count), filename: filename, mediaType: mediaType) } func coordinator() -> TeraAddMediaCoordinator { @@ -222,13 +222,17 @@ struct OfflineMediaFixture { actor OfflineMediaPicker: RadrootsMediaPicker { let byteSize: UInt64 + let filename: String + let mediaType: String private var denied = false private var available = true private(set) var importRequests: [RadrootsMediaImportRequest] = [] private(set) var captureRequests: [RadrootsMediaCaptureRequest] = [] - init(byteSize: UInt64) { + init(byteSize: UInt64, filename: String = "input.png", mediaType: String = "image/png") { self.byteSize = byteSize + self.filename = filename + self.mediaType = mediaType } func denyPermission() { @@ -257,8 +261,8 @@ actor OfflineMediaPicker: RadrootsMediaPicker { private func asset(_ source: RadrootsMediaSource) throws -> RadrootsMediaAsset { guard !denied else { throw RadrootsCaptureIntakeError.permissionDenied } guard available else { throw RadrootsCaptureIntakeError.unavailable } - return try RadrootsMediaAsset(source: source, kind: .image, file: RadrootsFileReference(scope: .cache, relativePath: "input.png"), - mediaType: "image/png", suggestedFilename: "input.png", sizeBytes: byteSize, + return try RadrootsMediaAsset(source: source, kind: .image, file: RadrootsFileReference(scope: .cache, relativePath: filename), + mediaType: mediaType, suggestedFilename: filename, sizeBytes: byteSize, pixelWidth: 2, pixelHeight: 2, capturedAt: Date(timeIntervalSince1970: 1_800_000_000)) } } diff --git a/TeraTests/TeraPublicDisclosureTests.swift b/TeraTests/TeraPublicDisclosureTests.swift @@ -0,0 +1,92 @@ +import ImageIO +@testable import TeraApp +import UIKit +import UniformTypeIdentifiers +import XCTest + +@MainActor +final class TeraPublicDisclosureTests: XCTestCase { + func testCameraAndLibraryStripSensitiveMetadataBeforeCreatingPublicMedia() async throws { + let source = try taggedImage() + let sourceProperties = try properties(source) + XCTAssertNotNil(sourceProperties[kCGImagePropertyGPSDictionary], "The fixture must actually contain GPS") + XCTAssertNotNil(sourceProperties[kCGImagePropertyTIFFDictionary]) + for camera in [false, true] { + let fixture = try OfflineMediaFixture(image: source, filename: "tagged.jpg", mediaType: "image/jpeg") + defer { fixture.remove() } + let coordinator = fixture.coordinator() + let photos = try await camera ? [coordinator.captureImage()] : coordinator.importImages(limit: 1) + let photo = try XCTUnwrap(photos.first) + let bytes = try Data(contentsOf: fixture.roots.stagedBlobsRoot.appendingPathComponent(photo.sha256)) + let output = try properties(bytes) + XCTAssertNil(output[kCGImagePropertyGPSDictionary]) + XCTAssertNil(output[kCGImagePropertyTIFFDictionary]) + XCTAssertNil(output[kCGImagePropertyIPTCDictionary]) + let exif = output[kCGImagePropertyExifDictionary] as? [CFString: Any] ?? [:] + XCTAssertNil(exif[kCGImagePropertyExifUserComment]) + XCTAssertNil(bytes.range(of: Data("C117_PRIVATE_CAMERA".utf8))) + XCTAssertNil(photo.remoteURL, "Local intake does not upload or publish") + XCTAssertEqual(photo.alt, "") + XCTAssertEqual(try Data(contentsOf: fixture.roots.cacheRoot.appendingPathComponent("tagged.jpg")), source) + let transfers = await fixture.transfer.enqueueCount + XCTAssertEqual(transfers, 0) + } + } + + func testLocationIsAbsentByDefaultAndOnlyManualTextSurvivesSaveAndRestart() async throws { + let fixture = try OfflineMediaFixture() + defer { fixture.remove() } + let client = TeraRuntimeClient.production() + let signer = ComposerForbiddenSigner() + let configuration = fixture.configuration(signer) + let snapshot = try await client.start(configuration: configuration) + var saved: [TeraComposerDraft] = [] + for type in TeraAddCommandType.allCases { + let store = TeraAddStore(runtimeClient: client, initialType: type) + store.configure(snapshot: snapshot) + await store.start() + XCTAssertNil(store.form.location) + store.updateForm(\.content, "local editing") + if type == .createEvent || type == .createFoodAvailability { + store.updateForm(\.location, "North side of the public market") + } + await store.save() + let draft = try XCTUnwrap(store.savedComposer) + XCTAssertEqual(draft.form.location, store.form.location) + saved.append(draft) + store.stop() + } + _ = try await client.stop() + _ = try await client.start(configuration: configuration) + for draft in saved { + let restored = try await client.loadComposer(scope: draft.scope, id: draft.id) + XCTAssertEqual(restored, draft) + } + let signs = await signer.requests + XCTAssertEqual(signs, 0) + _ = try await client.stop() + } + + private func properties(_ data: Data) throws -> [CFString: Any] { + let source = try XCTUnwrap(CGImageSourceCreateWithData(data as CFData, nil)) + return try XCTUnwrap(CGImageSourceCopyPropertiesAtIndex(source, 0, nil) as? [CFString: Any]) + } + + private func taggedImage() throws -> Data { + let image = UIGraphicsImageRenderer(size: CGSize(width: 2, height: 2)).image { context in + UIColor.green.setFill() + context.fill(CGRect(x: 0, y: 0, width: 2, height: 2)) + } + let output = NSMutableData() + let destination = try XCTUnwrap(CGImageDestinationCreateWithData(output, UTType.jpeg.identifier as CFString, 1, nil)) + let properties: [CFString: Any] = [ + kCGImagePropertyGPSDictionary: [kCGImagePropertyGPSLatitude: 12.3456, kCGImagePropertyGPSLatitudeRef: "N"], + kCGImagePropertyTIFFDictionary: [kCGImagePropertyTIFFMake: "C117_PRIVATE_CAMERA"], + kCGImagePropertyExifDictionary: [kCGImagePropertyExifUserComment: "C117_PRIVATE_CAMERA"], + kCGImagePropertyIPTCDictionary: [kCGImagePropertyIPTCCaptionAbstract: "C117_PRIVATE_CAMERA"], + ] + try CGImageDestinationAddImage(destination, XCTUnwrap(image.cgImage), properties as CFDictionary) + XCTAssertTrue(CGImageDestinationFinalize(destination)) + return output as Data + } +} diff --git a/project.yml b/project.yml @@ -116,6 +116,7 @@ targets: - path: TeraTests/TeraIdentityRecoveryTests.swift - path: TeraTests/TeraKeyRemovalTests.swift - path: TeraTests/TeraPublicationNavigationTests.swift + - path: TeraTests/TeraPublicDisclosureTests.swift - path: TeraTests/TeraIdentityCancellationTests.swift - path: TeraTests/TeraTodayStoreTests.swift - path: TeraTests/TeraTodaySyncFixtures.swift diff --git a/release/provenance.json b/release/provenance.json @@ -6,7 +6,7 @@ "info_plist_sha256": "15ef08b1cdd1096cfb9eeaf5be5bf8f814807a7ca9350bbbb47860fa72ec13ef", "privacy_manifest_sha256": "a331d51864743ebe4e00dd22360b4a538b6b3ac26a6b3eb54094e60a36959a12", "sbom_sha256": "904302bddb22ac1e4f9e79ab1145e590a8eeac7dd75e42539fb72eb0b7ebd4b1", - "xcode_project_sha256": "085b1ac33c28beffcc9c551dff69ff7323eea99469e7092bb61998db2f8f0c9e" + "xcode_project_sha256": "684bd9e7f9b2ea18781a24ec857129a0c1b422b7a1b27cbb119675a1ebe74523" }, "disposition": "unsigned", "platforms": [ diff --git a/test-fixtures/swiftlint-maintainability-baseline.v1.json b/test-fixtures/swiftlint-maintainability-baseline.v1.json @@ -1 +1 @@ -[{"violation":{"ruleDescription":"Function bodies should not span too many lines","ruleIdentifier":"function_body_length","ruleName":"Function Body Length","reason":"Initializer body should span 50 lines or less excluding comments and whitespace: currently spans 52 lines","location":{"line":21,"character":3,"file":"Tera/App/TeraAppModel.swift"},"severity":"error"},"text":" init("},{"violation":{"ruleDescription":"Type bodies should not span too many lines","ruleIdentifier":"type_body_length","ruleName":"Type Body Length","reason":"Class body should span 350 lines or less excluding comments and whitespace: currently spans 373 lines","location":{"line":31,"character":7,"file":"Tera/Runtime/TeraGeneratedRuntimeBackend.swift"},"severity":"error"},"text":"final class TeraGeneratedRuntimeBackend: TeraRuntimeBackend, @unchecked Sendable {"},{"violation":{"ruleDescription":"Files should not span too many lines.","ruleIdentifier":"file_length","ruleName":"File Length","reason":"File should contain 1000 lines or less: currently contains 1089","location":{"line":1089,"character":1,"file":"Tera/Runtime/TeraGeneratedRuntimeBackend.swift"},"severity":"error"},"text":"}"},{"violation":{"ruleDescription":"Type bodies should not span too many lines","ruleIdentifier":"type_body_length","ruleName":"Type Body Length","reason":"Actor body should span 350 lines or less excluding comments and whitespace: currently spans 675 lines","location":{"line":3,"character":1,"file":"Tera/Runtime/TeraRuntimeClient.swift"},"severity":"error"},"text":"actor TeraRuntimeClient {"},{"violation":{"ruleDescription":"Function bodies should not span too many lines","ruleIdentifier":"function_body_length","ruleName":"Function Body Length","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 70 lines","location":{"line":353,"character":3,"file":"Tera/Runtime/TeraRuntimeClient.swift"},"severity":"error"},"text":" func changes(bufferCapacity: Int = 16) async throws -> AsyncStream<TeraRuntimeChange> {"},{"violation":{"ruleDescription":"Files should not span too many lines.","ruleIdentifier":"file_length","ruleName":"File Length","reason":"File should contain 400 lines or less: currently contains 749","location":{"line":749,"character":1,"file":"Tera/Runtime/TeraRuntimeClient.swift"},"severity":"error"},"text":"}"},{"violation":{"ruleDescription":"Files should not span too many lines.","ruleIdentifier":"file_length","ruleName":"File Length","reason":"File should contain 1000 lines or less: currently contains 1029","location":{"line":1029,"character":1,"file":"Tera/Runtime/TeraRuntimeModels.swift"},"severity":"error"},"text":"}"},{"violation":{"ruleDescription":"Type bodies should not span too many lines","ruleIdentifier":"type_body_length","ruleName":"Type Body Length","reason":"Class body should span 350 lines or less excluding comments and whitespace: currently spans 491 lines","location":{"line":4,"character":7,"file":"Tera/State/TeraAddStore.swift"},"severity":"error"},"text":"final class TeraAddStore: ObservableObject {"},{"violation":{"ruleDescription":"Files should not span too many lines.","ruleIdentifier":"file_length","ruleName":"File Length","reason":"File should contain 400 lines or less: currently contains 702","location":{"line":702,"character":1,"file":"Tera/State/TeraAddStore.swift"},"severity":"error"},"text":"}"},{"violation":{"ruleDescription":"Type bodies should not span too many lines","ruleIdentifier":"type_body_length","ruleName":"Type Body Length","reason":"Actor body should span 350 lines or less excluding comments and whitespace: currently spans 468 lines","location":{"line":78,"character":1,"file":"Tera/State/TeraConfigurationStore.swift"},"severity":"error"},"text":"actor TeraConfigurationStore {"},{"violation":{"ruleDescription":"Function bodies should not span too many lines","ruleIdentifier":"function_body_length","ruleName":"Function Body Length","reason":"Function body should span 100 lines or less excluding comments and whitespace: currently spans 109 lines","location":{"line":148,"character":5,"file":"Tera/State/TeraConfigurationStore.swift"},"severity":"error"},"text":" func load() throws -> TeraAppConfiguration {"},{"violation":{"ruleDescription":"Files should not span too many lines.","ruleIdentifier":"file_length","ruleName":"File Length","reason":"File should contain 400 lines or less: currently contains 759","location":{"line":759,"character":1,"file":"Tera/State/TeraConfigurationStore.swift"},"severity":"error"},"text":"}"},{"violation":{"ruleDescription":"Type bodies should not span too many lines","ruleIdentifier":"type_body_length","ruleName":"Type Body Length","reason":"Actor body should span 350 lines or less excluding comments and whitespace: currently spans 390 lines","location":{"line":39,"character":1,"file":"Tera/State/TeraSessionStore.swift"},"severity":"error"},"text":"actor TeraSessionStore {"},{"violation":{"ruleDescription":"Complexity of function bodies should be limited.","ruleIdentifier":"cyclomatic_complexity","ruleName":"Cyclomatic Complexity","reason":"Function should have complexity 10 or less; currently complexity is 13","location":{"line":125,"character":11,"file":"Tera/State/TeraSessionStore.swift"},"severity":"error"},"text":" private func start(acceptingReconfiguration: Bool) async -> TeraSessionPhase {"},{"violation":{"ruleDescription":"Files should not span too many lines.","ruleIdentifier":"file_length","ruleName":"File Length","reason":"File should contain 400 lines or less: currently contains 452","location":{"line":452,"character":1,"file":"Tera/State/TeraSessionStore.swift"},"severity":"error"},"text":"}"},{"violation":{"ruleDescription":"Type bodies should not span too many lines","ruleIdentifier":"type_body_length","ruleName":"Type Body Length","reason":"Struct body should span 250 lines or less excluding comments and whitespace: currently spans 342 lines","location":{"line":4,"character":1,"file":"Tera/Views/TeraAddView.swift"},"severity":"error"},"text":"struct TeraAddView: View {"},{"violation":{"ruleDescription":"Type bodies should not span too many lines","ruleIdentifier":"type_body_length","ruleName":"Type Body Length","reason":"Struct body should span 250 lines or less excluding comments and whitespace: currently spans 259 lines","location":{"line":365,"character":1,"file":"Tera/Views/TeraSupportingViews.swift"},"severity":"error"},"text":"struct TeraSettingsView: View {"},{"violation":{"ruleDescription":"Files should not span too many lines.","ruleIdentifier":"file_length","ruleName":"File Length","reason":"File should contain 400 lines or less: currently contains 629","location":{"line":629,"character":1,"file":"Tera/Views/TeraSupportingViews.swift"},"severity":"error"},"text":"}"},{"violation":{"ruleDescription":"Type bodies should not span too many lines","ruleIdentifier":"type_body_length","ruleName":"Type Body Length","reason":"Class body should span 350 lines or less excluding comments and whitespace: currently spans 607 lines","location":{"line":6,"character":7,"file":"TeraTests/TeraAddStoreTests.swift"},"severity":"error"},"text":"final class TeraAddStoreTests: XCTestCase {"},{"violation":{"ruleDescription":"Type bodies should not span too many lines","ruleIdentifier":"type_body_length","ruleName":"Type Body Length","reason":"Actor body should span 350 lines or less excluding comments and whitespace: currently spans 394 lines","location":{"line":682,"character":1,"file":"TeraTests/TeraAddStoreTests.swift"},"severity":"error"},"text":"actor AddBackend: TeraRuntimeBackend {"},{"violation":{"ruleDescription":"Number of function parameters should be low.","ruleIdentifier":"function_parameter_count","ruleName":"Function Parameter Count","reason":"Function should have 5 parameters or less: it currently has 8","location":{"line":1020,"character":3,"file":"TeraTests/TeraAddStoreTests.swift"},"severity":"error"},"text":" func makeStatus("},{"violation":{"ruleDescription":"Files should not span too many lines.","ruleIdentifier":"file_length","ruleName":"File Length","reason":"File should contain 1000 lines or less: currently contains 1232","location":{"line":1232,"character":1,"file":"TeraTests/TeraAddStoreTests.swift"},"severity":"error"},"text":"}"},{"violation":{"ruleDescription":"Type bodies should not span too many lines","ruleIdentifier":"type_body_length","ruleName":"Type Body Length","reason":"Class body should span 250 lines or less excluding comments and whitespace: currently spans 307 lines","location":{"line":7,"character":7,"file":"TeraTests/TeraRemoteQualificationTests.swift"},"severity":"error"},"text":"final class TeraRemoteQualificationTests: XCTestCase {"},{"violation":{"ruleDescription":"Function bodies should not span too many lines","ruleIdentifier":"function_body_length","ruleName":"Function Body Length","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 94 lines","location":{"line":45,"character":3,"file":"TeraTests/TeraRemoteQualificationTests.swift"},"severity":"error"},"text":" func testQualificationRejectsAmbiguousOrUnsafeHarnessValues() {"},{"violation":{"ruleDescription":"Function bodies should not span too many lines","ruleIdentifier":"function_body_length","ruleName":"Function Body Length","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 53 lines","location":{"line":212,"character":3,"file":"TeraTests/TeraRemoteQualificationTests.swift"},"severity":"error"},"text":" func testAuthorizationEvidenceIsRedactedEphemeralAndRelaunchCleaned() throws {"},{"violation":{"ruleDescription":"Type bodies should not span too many lines","ruleIdentifier":"type_body_length","ruleName":"Type Body Length","reason":"Class body should span 250 lines or less excluding comments and whitespace: currently spans 298 lines","location":{"line":4,"character":7,"file":"TeraTests/TeraRuntimeClientTests.swift"},"severity":"error"},"text":"final class TeraRuntimeClientTests: XCTestCase {"},{"violation":{"ruleDescription":"Type bodies should not span too many lines","ruleIdentifier":"type_body_length","ruleName":"Type Body Length","reason":"Class body should span 350 lines or less excluding comments and whitespace: currently spans 580 lines","location":{"line":6,"character":7,"file":"TeraTests/TeraStateMigrationTests.swift"},"severity":"error"},"text":"final class TeraStateMigrationTests: XCTestCase {"},{"violation":{"ruleDescription":"Function bodies should not span too many lines","ruleIdentifier":"function_body_length","ruleName":"Function Body Length","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 61 lines","location":{"line":7,"character":5,"file":"TeraTests/TeraStateMigrationTests.swift"},"severity":"error"},"text":" func testRelayValidationMatchesRuntimeProfiles() throws {"},{"violation":{"ruleDescription":"Files should not span too many lines.","ruleIdentifier":"file_length","ruleName":"File Length","reason":"File should contain 400 lines or less: currently contains 622","location":{"line":622,"character":1,"file":"TeraTests/TeraStateMigrationTests.swift"},"severity":"error"},"text":"}"},{"violation":{"ruleDescription":"Type bodies should not span too many lines","ruleIdentifier":"type_body_length","ruleName":"Type Body Length","reason":"Actor body should span 250 lines or less excluding comments and whitespace: currently spans 252 lines","location":{"line":207,"character":9,"file":"TeraTests/TeraSupportingStoreTests.swift"},"severity":"error"},"text":"private actor SupportingBackend: TeraRuntimeBackend {"},{"violation":{"ruleDescription":"Files should not span too many lines.","ruleIdentifier":"file_length","ruleName":"File Length","reason":"File should contain 400 lines or less: currently contains 486","location":{"line":486,"character":1,"file":"TeraTests/TeraSupportingStoreTests.swift"},"severity":"error"},"text":"}"},{"violation":{"ruleDescription":"Type bodies should not span too many lines","ruleIdentifier":"type_body_length","ruleName":"Type Body Length","reason":"Class body should span 350 lines or less excluding comments and whitespace: currently spans 1365 lines","location":{"line":4,"character":7,"file":"TeraUITests/TeraRemoteQualificationUITests.swift"},"severity":"error"},"text":"final class TeraRemoteQualificationUITests: XCTestCase {"},{"violation":{"ruleDescription":"Function bodies should not span too many lines","ruleIdentifier":"function_body_length","ruleName":"Function Body Length","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 65 lines","location":{"line":62,"character":3,"file":"TeraUITests/TeraRemoteQualificationUITests.swift"},"severity":"error"},"text":" func testLocalSocialFiveFlowScenario() throws {"},{"violation":{"ruleDescription":"Function bodies should not span too many lines","ruleIdentifier":"function_body_length","ruleName":"Function Body Length","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 66 lines","location":{"line":166,"character":3,"file":"TeraUITests/TeraRemoteQualificationUITests.swift"},"severity":"error"},"text":" func testLocalSocialDeterministicPersonas() throws {"},{"violation":{"ruleDescription":"Function bodies should not span too many lines","ruleIdentifier":"function_body_length","ruleName":"Function Body Length","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 54 lines","location":{"line":239,"character":3,"file":"TeraUITests/TeraRemoteQualificationUITests.swift"},"severity":"error"},"text":" func testRemoteBlossomUploadAndRecovery() throws {"},{"violation":{"ruleDescription":"Complexity of function bodies should be limited.","ruleIdentifier":"cyclomatic_complexity","ruleName":"Cyclomatic Complexity","reason":"Function should have complexity 10 or less; currently complexity is 18","location":{"line":566,"character":11,"file":"TeraUITests/TeraRemoteQualificationUITests.swift"},"severity":"error"},"text":" private func expectedAddFieldLabel(_ identifier: String, type: String) -> String {"},{"violation":{"ruleDescription":"Function bodies should not span too many lines","ruleIdentifier":"function_body_length","ruleName":"Function Body Length","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 67 lines","location":{"line":1601,"character":10,"file":"TeraUITests/TeraRemoteQualificationUITests.swift"},"severity":"error"},"text":" static func environment("},{"violation":{"ruleDescription":"Files should not span too many lines.","ruleIdentifier":"file_length","ruleName":"File Length","reason":"File should contain 1000 lines or less: currently contains 2077","location":{"line":2077,"character":1,"file":"TeraUITests/TeraRemoteQualificationUITests.swift"},"severity":"error"},"text":"}"}] +[{"violation":{"ruleDescription":"Function bodies should not span too many lines","ruleIdentifier":"function_body_length","ruleName":"Function Body Length","reason":"Initializer body should span 50 lines or less excluding comments and whitespace: currently spans 52 lines","location":{"line":21,"character":3,"file":"Tera/App/TeraAppModel.swift"},"severity":"error"},"text":" init("},{"violation":{"ruleDescription":"Type bodies should not span too many lines","ruleIdentifier":"type_body_length","ruleName":"Type Body Length","reason":"Class body should span 350 lines or less excluding comments and whitespace: currently spans 373 lines","location":{"line":31,"character":7,"file":"Tera/Runtime/TeraGeneratedRuntimeBackend.swift"},"severity":"error"},"text":"final class TeraGeneratedRuntimeBackend: TeraRuntimeBackend, @unchecked Sendable {"},{"violation":{"ruleDescription":"Files should not span too many lines.","ruleIdentifier":"file_length","ruleName":"File Length","reason":"File should contain 1000 lines or less: currently contains 1089","location":{"line":1089,"character":1,"file":"Tera/Runtime/TeraGeneratedRuntimeBackend.swift"},"severity":"error"},"text":"}"},{"violation":{"ruleDescription":"Type bodies should not span too many lines","ruleIdentifier":"type_body_length","ruleName":"Type Body Length","reason":"Actor body should span 350 lines or less excluding comments and whitespace: currently spans 675 lines","location":{"line":3,"character":1,"file":"Tera/Runtime/TeraRuntimeClient.swift"},"severity":"error"},"text":"actor TeraRuntimeClient {"},{"violation":{"ruleDescription":"Function bodies should not span too many lines","ruleIdentifier":"function_body_length","ruleName":"Function Body Length","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 70 lines","location":{"line":353,"character":3,"file":"Tera/Runtime/TeraRuntimeClient.swift"},"severity":"error"},"text":" func changes(bufferCapacity: Int = 16) async throws -> AsyncStream<TeraRuntimeChange> {"},{"violation":{"ruleDescription":"Files should not span too many lines.","ruleIdentifier":"file_length","ruleName":"File Length","reason":"File should contain 400 lines or less: currently contains 749","location":{"line":749,"character":1,"file":"Tera/Runtime/TeraRuntimeClient.swift"},"severity":"error"},"text":"}"},{"violation":{"ruleDescription":"Files should not span too many lines.","ruleIdentifier":"file_length","ruleName":"File Length","reason":"File should contain 1000 lines or less: currently contains 1029","location":{"line":1029,"character":1,"file":"Tera/Runtime/TeraRuntimeModels.swift"},"severity":"error"},"text":"}"},{"violation":{"ruleDescription":"Type bodies should not span too many lines","ruleIdentifier":"type_body_length","ruleName":"Type Body Length","reason":"Class body should span 350 lines or less excluding comments and whitespace: currently spans 491 lines","location":{"line":4,"character":7,"file":"Tera/State/TeraAddStore.swift"},"severity":"error"},"text":"final class TeraAddStore: ObservableObject {"},{"violation":{"ruleDescription":"Files should not span too many lines.","ruleIdentifier":"file_length","ruleName":"File Length","reason":"File should contain 400 lines or less: currently contains 702","location":{"line":702,"character":1,"file":"Tera/State/TeraAddStore.swift"},"severity":"error"},"text":"}"},{"violation":{"ruleDescription":"Type bodies should not span too many lines","ruleIdentifier":"type_body_length","ruleName":"Type Body Length","reason":"Actor body should span 350 lines or less excluding comments and whitespace: currently spans 468 lines","location":{"line":78,"character":1,"file":"Tera/State/TeraConfigurationStore.swift"},"severity":"error"},"text":"actor TeraConfigurationStore {"},{"violation":{"ruleDescription":"Function bodies should not span too many lines","ruleIdentifier":"function_body_length","ruleName":"Function Body Length","reason":"Function body should span 100 lines or less excluding comments and whitespace: currently spans 109 lines","location":{"line":148,"character":5,"file":"Tera/State/TeraConfigurationStore.swift"},"severity":"error"},"text":" func load() throws -> TeraAppConfiguration {"},{"violation":{"ruleDescription":"Files should not span too many lines.","ruleIdentifier":"file_length","ruleName":"File Length","reason":"File should contain 400 lines or less: currently contains 759","location":{"line":759,"character":1,"file":"Tera/State/TeraConfigurationStore.swift"},"severity":"error"},"text":"}"},{"violation":{"ruleDescription":"Type bodies should not span too many lines","ruleIdentifier":"type_body_length","ruleName":"Type Body Length","reason":"Actor body should span 350 lines or less excluding comments and whitespace: currently spans 390 lines","location":{"line":39,"character":1,"file":"Tera/State/TeraSessionStore.swift"},"severity":"error"},"text":"actor TeraSessionStore {"},{"violation":{"ruleDescription":"Complexity of function bodies should be limited.","ruleIdentifier":"cyclomatic_complexity","ruleName":"Cyclomatic Complexity","reason":"Function should have complexity 10 or less; currently complexity is 13","location":{"line":125,"character":11,"file":"Tera/State/TeraSessionStore.swift"},"severity":"error"},"text":" private func start(acceptingReconfiguration: Bool) async -> TeraSessionPhase {"},{"violation":{"ruleDescription":"Files should not span too many lines.","ruleIdentifier":"file_length","ruleName":"File Length","reason":"File should contain 400 lines or less: currently contains 452","location":{"line":452,"character":1,"file":"Tera/State/TeraSessionStore.swift"},"severity":"error"},"text":"}"},{"violation":{"ruleDescription":"Type bodies should not span too many lines","ruleIdentifier":"type_body_length","ruleName":"Type Body Length","reason":"Struct body should span 250 lines or less excluding comments and whitespace: currently spans 259 lines","location":{"line":365,"character":1,"file":"Tera/Views/TeraSupportingViews.swift"},"severity":"error"},"text":"struct TeraSettingsView: View {"},{"violation":{"ruleDescription":"Files should not span too many lines.","ruleIdentifier":"file_length","ruleName":"File Length","reason":"File should contain 400 lines or less: currently contains 629","location":{"line":629,"character":1,"file":"Tera/Views/TeraSupportingViews.swift"},"severity":"error"},"text":"}"},{"violation":{"ruleDescription":"Type bodies should not span too many lines","ruleIdentifier":"type_body_length","ruleName":"Type Body Length","reason":"Class body should span 350 lines or less excluding comments and whitespace: currently spans 607 lines","location":{"line":6,"character":7,"file":"TeraTests/TeraAddStoreTests.swift"},"severity":"error"},"text":"final class TeraAddStoreTests: XCTestCase {"},{"violation":{"ruleDescription":"Type bodies should not span too many lines","ruleIdentifier":"type_body_length","ruleName":"Type Body Length","reason":"Actor body should span 350 lines or less excluding comments and whitespace: currently spans 394 lines","location":{"line":682,"character":1,"file":"TeraTests/TeraAddStoreTests.swift"},"severity":"error"},"text":"actor AddBackend: TeraRuntimeBackend {"},{"violation":{"ruleDescription":"Number of function parameters should be low.","ruleIdentifier":"function_parameter_count","ruleName":"Function Parameter Count","reason":"Function should have 5 parameters or less: it currently has 8","location":{"line":1020,"character":3,"file":"TeraTests/TeraAddStoreTests.swift"},"severity":"error"},"text":" func makeStatus("},{"violation":{"ruleDescription":"Files should not span too many lines.","ruleIdentifier":"file_length","ruleName":"File Length","reason":"File should contain 1000 lines or less: currently contains 1232","location":{"line":1232,"character":1,"file":"TeraTests/TeraAddStoreTests.swift"},"severity":"error"},"text":"}"},{"violation":{"ruleDescription":"Type bodies should not span too many lines","ruleIdentifier":"type_body_length","ruleName":"Type Body Length","reason":"Class body should span 250 lines or less excluding comments and whitespace: currently spans 307 lines","location":{"line":7,"character":7,"file":"TeraTests/TeraRemoteQualificationTests.swift"},"severity":"error"},"text":"final class TeraRemoteQualificationTests: XCTestCase {"},{"violation":{"ruleDescription":"Function bodies should not span too many lines","ruleIdentifier":"function_body_length","ruleName":"Function Body Length","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 94 lines","location":{"line":45,"character":3,"file":"TeraTests/TeraRemoteQualificationTests.swift"},"severity":"error"},"text":" func testQualificationRejectsAmbiguousOrUnsafeHarnessValues() {"},{"violation":{"ruleDescription":"Function bodies should not span too many lines","ruleIdentifier":"function_body_length","ruleName":"Function Body Length","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 53 lines","location":{"line":212,"character":3,"file":"TeraTests/TeraRemoteQualificationTests.swift"},"severity":"error"},"text":" func testAuthorizationEvidenceIsRedactedEphemeralAndRelaunchCleaned() throws {"},{"violation":{"ruleDescription":"Type bodies should not span too many lines","ruleIdentifier":"type_body_length","ruleName":"Type Body Length","reason":"Class body should span 250 lines or less excluding comments and whitespace: currently spans 298 lines","location":{"line":4,"character":7,"file":"TeraTests/TeraRuntimeClientTests.swift"},"severity":"error"},"text":"final class TeraRuntimeClientTests: XCTestCase {"},{"violation":{"ruleDescription":"Type bodies should not span too many lines","ruleIdentifier":"type_body_length","ruleName":"Type Body Length","reason":"Class body should span 350 lines or less excluding comments and whitespace: currently spans 580 lines","location":{"line":6,"character":7,"file":"TeraTests/TeraStateMigrationTests.swift"},"severity":"error"},"text":"final class TeraStateMigrationTests: XCTestCase {"},{"violation":{"ruleDescription":"Function bodies should not span too many lines","ruleIdentifier":"function_body_length","ruleName":"Function Body Length","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 61 lines","location":{"line":7,"character":5,"file":"TeraTests/TeraStateMigrationTests.swift"},"severity":"error"},"text":" func testRelayValidationMatchesRuntimeProfiles() throws {"},{"violation":{"ruleDescription":"Files should not span too many lines.","ruleIdentifier":"file_length","ruleName":"File Length","reason":"File should contain 400 lines or less: currently contains 622","location":{"line":622,"character":1,"file":"TeraTests/TeraStateMigrationTests.swift"},"severity":"error"},"text":"}"},{"violation":{"ruleDescription":"Type bodies should not span too many lines","ruleIdentifier":"type_body_length","ruleName":"Type Body Length","reason":"Actor body should span 250 lines or less excluding comments and whitespace: currently spans 252 lines","location":{"line":207,"character":9,"file":"TeraTests/TeraSupportingStoreTests.swift"},"severity":"error"},"text":"private actor SupportingBackend: TeraRuntimeBackend {"},{"violation":{"ruleDescription":"Files should not span too many lines.","ruleIdentifier":"file_length","ruleName":"File Length","reason":"File should contain 400 lines or less: currently contains 486","location":{"line":486,"character":1,"file":"TeraTests/TeraSupportingStoreTests.swift"},"severity":"error"},"text":"}"},{"violation":{"ruleDescription":"Type bodies should not span too many lines","ruleIdentifier":"type_body_length","ruleName":"Type Body Length","reason":"Class body should span 350 lines or less excluding comments and whitespace: currently spans 1365 lines","location":{"line":4,"character":7,"file":"TeraUITests/TeraRemoteQualificationUITests.swift"},"severity":"error"},"text":"final class TeraRemoteQualificationUITests: XCTestCase {"},{"violation":{"ruleDescription":"Function bodies should not span too many lines","ruleIdentifier":"function_body_length","ruleName":"Function Body Length","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 65 lines","location":{"line":62,"character":3,"file":"TeraUITests/TeraRemoteQualificationUITests.swift"},"severity":"error"},"text":" func testLocalSocialFiveFlowScenario() throws {"},{"violation":{"ruleDescription":"Function bodies should not span too many lines","ruleIdentifier":"function_body_length","ruleName":"Function Body Length","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 66 lines","location":{"line":166,"character":3,"file":"TeraUITests/TeraRemoteQualificationUITests.swift"},"severity":"error"},"text":" func testLocalSocialDeterministicPersonas() throws {"},{"violation":{"ruleDescription":"Function bodies should not span too many lines","ruleIdentifier":"function_body_length","ruleName":"Function Body Length","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 54 lines","location":{"line":239,"character":3,"file":"TeraUITests/TeraRemoteQualificationUITests.swift"},"severity":"error"},"text":" func testRemoteBlossomUploadAndRecovery() throws {"},{"violation":{"ruleDescription":"Complexity of function bodies should be limited.","ruleIdentifier":"cyclomatic_complexity","ruleName":"Cyclomatic Complexity","reason":"Function should have complexity 10 or less; currently complexity is 18","location":{"line":566,"character":11,"file":"TeraUITests/TeraRemoteQualificationUITests.swift"},"severity":"error"},"text":" private func expectedAddFieldLabel(_ identifier: String, type: String) -> String {"},{"violation":{"ruleDescription":"Function bodies should not span too many lines","ruleIdentifier":"function_body_length","ruleName":"Function Body Length","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 67 lines","location":{"line":1601,"character":10,"file":"TeraUITests/TeraRemoteQualificationUITests.swift"},"severity":"error"},"text":" static func environment("},{"violation":{"ruleDescription":"Files should not span too many lines.","ruleIdentifier":"file_length","ruleName":"File Length","reason":"File should contain 1000 lines or less: currently contains 2077","location":{"line":2077,"character":1,"file":"TeraUITests/TeraRemoteQualificationUITests.swift"},"severity":"error"},"text":"}"}]