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 7ad4815ea00185d58b0d70649924fa629d88a505
parent 97b38cecda3a43ef5f5f73a7e21cef1811c0886a
Author: triesap <tyson@radroots.org>
Date:   Wed,  9 Sep 2026 16:28:14 +0000

ios: decouple local product startup from refresh

- Start Today and Add concurrently after the authorized app transition.
- Own and coalesce startup tasks across cancellation and lifecycle changes.
- Reject stale startup completion while preserving unsent Add editing.
- Verify stalled refresh, replacement startup and identity authorization behavior.

Diffstat:
MPackage.swift | 1+
MTera.xcodeproj/project.pbxproj | 4++++
MTera/App/TeraAppModel.swift | 65+++--------------------------------------------------------------
ATera/App/TeraProductStores.swift | 101+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ATeraTests/TeraProductStartupTests.swift | 166+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
MTeraTests/TeraScopedStoreTests.swift | 17+++++++++++++----
MTeraTests/TeraSessionResourceTests.swift | 60+++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Mproject.yml | 1+
Mrelease/provenance.json | 2+-
Mtest-fixtures/swiftlint-maintainability-baseline.v1.json | 4++--
10 files changed, 349 insertions(+), 72 deletions(-)

diff --git a/Package.swift b/Package.swift @@ -49,6 +49,7 @@ let package = Package( "App/AppEntry.swift", "App/TeraAppDelegate.swift", "App/TeraAppModel.swift", + "App/TeraProductStores.swift", "App/TeraProvider.swift", "App/TeraRemoteQualification.swift", "App/TeraRemoteQualificationEvidence.swift", diff --git a/Tera.xcodeproj/project.pbxproj b/Tera.xcodeproj/project.pbxproj @@ -49,6 +49,7 @@ BB71614D2A6017C071A39BED /* TeraMutationAdmissionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02F322CF98F8BEB908B93215 /* TeraMutationAdmissionTests.swift */; }; C219B5F4E24188E1116BF45B /* TeraRuntimeClientFixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88ED360C84532DA7088BFC6E /* TeraRuntimeClientFixtures.swift */; }; C3177E827FE62639579C3EF5 /* TeraRemoteQualificationUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32CBDAE974FB8D7408108E69 /* TeraRemoteQualificationUITests.swift */; }; + C46F8EB5B196FFDD37E0279A /* TeraProductStartupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF60EF9DCFDC4ECE77AED67 /* TeraProductStartupTests.swift */; }; CDBE38E6B521611436F28858 /* TeraMediaStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61B6F0B16BFD8F046E9C163F /* TeraMediaStoreTests.swift */; }; D64AC2FDC6FE05A39114D2DB /* TeraMediaFileFixture.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3C914B349DAE2EF66658229 /* TeraMediaFileFixture.swift */; }; D918A664D9A4B9859A1A6503 /* TeraErrorRecoveryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93A3358AFC23C567DB9CB02F /* TeraErrorRecoveryTests.swift */; }; @@ -107,6 +108,7 @@ 73EBD10E201ECA26DCED523F /* TeraRuntimeInvalidationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeraRuntimeInvalidationTests.swift; sourceTree = "<group>"; }; 74FCF1C4A8A7137623BBE223 /* TeraOpenedMediaCloseFixture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeraOpenedMediaCloseFixture.swift; sourceTree = "<group>"; }; 794D475C70BA8ED994D1CA66 /* TeraTodayStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeraTodayStoreTests.swift; sourceTree = "<group>"; }; + 7AF60EF9DCFDC4ECE77AED67 /* TeraProductStartupTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeraProductStartupTests.swift; sourceTree = "<group>"; }; 7C0836F5D5F71F9A54795C57 /* TeraSessionGenerationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeraSessionGenerationTests.swift; sourceTree = "<group>"; }; 8591841AD9AB9B718A37EB47 /* TeraRuntimeBackpressureTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeraRuntimeBackpressureTests.swift; sourceTree = "<group>"; }; 88ED360C84532DA7088BFC6E /* TeraRuntimeClientFixtures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeraRuntimeClientFixtures.swift; sourceTree = "<group>"; }; @@ -214,6 +216,7 @@ 21DD785439F415278214D746 /* TeraObservationCoalescingTests.swift */, 74FCF1C4A8A7137623BBE223 /* TeraOpenedMediaCloseFixture.swift */, 9B10B1F3C7FCD9A72F04FC00 /* TeraOpenedMediaTests.swift */, + 7AF60EF9DCFDC4ECE77AED67 /* TeraProductStartupTests.swift */, E351F52E77C74A4E99095E45 /* TeraRemoteQualificationTests.swift */, 6B8677670E203FF68EC88983 /* TeraRootShellTests.swift */, 8591841AD9AB9B718A37EB47 /* TeraRuntimeBackpressureTests.swift */, @@ -464,6 +467,7 @@ AC33144E09CB0384C4FB090C /* TeraObservationCoalescingTests.swift in Sources */, 2A68D8FEEC3B88D47D696589 /* TeraOpenedMediaCloseFixture.swift in Sources */, 6FED2D2255CC702B33CDC760 /* TeraOpenedMediaTests.swift in Sources */, + C46F8EB5B196FFDD37E0279A /* TeraProductStartupTests.swift in Sources */, 81AF9DDA0E64DC4EEFCEF25D /* TeraRemoteQualificationTests.swift in Sources */, 9AD968F0B37C64B5AF8D9A68 /* TeraRootShellTests.swift in Sources */, 2B50FA35AD753EFA15CF00CD /* TeraRuntimeBackpressureTests.swift in Sources */, diff --git a/Tera/App/TeraAppModel.swift b/Tera/App/TeraAppModel.swift @@ -2,68 +2,6 @@ import Foundation import RadrootsKit @MainActor -final class TeraProductStores { - let today: TeraTodayStore - let add: TeraAddStore - let search: TeraSearchStore - let me: TeraMeStore - let settings: TeraSettingsStore - let media: TeraMediaStore - private var generation = TeraSessionGeneration.initial - - init( - runtimeClient: TeraRuntimeClient, - addMedia: (any TeraAddMediaHandling)? = nil - ) { - today = TeraTodayStore(runtimeClient: runtimeClient) - add = TeraAddStore(runtimeClient: runtimeClient, media: addMedia) - search = TeraSearchStore(runtimeClient: runtimeClient) - me = TeraMeStore(runtimeClient: runtimeClient) - settings = TeraSettingsStore(runtimeClient: runtimeClient) - media = TeraMediaStore(runtimeClient: runtimeClient) - } - - func configure(snapshot: TeraRuntimeSnapshot) { - generation = generation.invalidated() - today.configure(snapshot: snapshot) - add.configure(snapshot: snapshot) - search.stop() - search.configure(context: today.selectedContext) - me.stop() - me.configure(context: today.selectedContext) - settings.configure(snapshot: snapshot) - media.configure(snapshot: snapshot) - } - - func resume() async { - let requested = generation - await today.start() - guard generation == requested, generation.isActive, !Task.isCancelled else { return } - await add.start() - } - - func suspend() { - generation = generation.invalidated() - today.stop() - add.suspend() - search.stop() - me.stop() - settings.stop() - media.reset() - } - - func stop() { - generation = generation.invalidated() - today.stop() - add.stop() - search.stop() - me.stop() - settings.stop() - media.reset() - } -} - -@MainActor final class TeraAppModel: ObservableObject { typealias Phase = TeraSessionPhase @@ -274,6 +212,9 @@ final class TeraAppModel: ObservableObject { productStores?.configure(snapshot: snapshot) } phase = result + if case .running = result { + productStores?.start() + } await lifecycleCoordinator.record( "ios.lifecycle.operation_completed", level: Self.isFailure(result) ? .warning : .info, diff --git a/Tera/App/TeraProductStores.swift b/Tera/App/TeraProductStores.swift @@ -0,0 +1,101 @@ +import Foundation + +@MainActor +final class TeraProductStores { + let today: TeraTodayStore + let add: TeraAddStore + let search: TeraSearchStore + let me: TeraMeStore + let settings: TeraSettingsStore + let media: TeraMediaStore + private var generation = TeraSessionGeneration.initial + private var startupTask: Task<Void, Never>? + + init( + runtimeClient: TeraRuntimeClient, + addMedia: (any TeraAddMediaHandling)? = nil + ) { + today = TeraTodayStore(runtimeClient: runtimeClient) + add = TeraAddStore(runtimeClient: runtimeClient, media: addMedia) + search = TeraSearchStore(runtimeClient: runtimeClient) + me = TeraMeStore(runtimeClient: runtimeClient) + settings = TeraSettingsStore(runtimeClient: runtimeClient) + media = TeraMediaStore(runtimeClient: runtimeClient) + } + + deinit { + startupTask?.cancel() + } + + func configure(snapshot: TeraRuntimeSnapshot) { + let wasStarting = startupTask != nil + cancelStartup() + if wasStarting { + today.stop() + add.suspend() + } + today.configure(snapshot: snapshot) + add.configure(snapshot: snapshot) + search.stop() + search.configure(context: today.selectedContext) + me.stop() + me.configure(context: today.selectedContext) + settings.configure(snapshot: snapshot) + media.configure(snapshot: snapshot) + } + + func start() { + guard startupTask == nil, generation.isActive, !Task.isCancelled else { return } + generation = generation.invalidated() + guard generation.isActive else { return } + let requested = generation + startupTask = Task { [weak self, today, add] in + async let todayStart: Void = today.start() + async let addStart: Void = add.start() + _ = await (todayStart, addStart) + self?.finishStartup(requested, cancelled: Task.isCancelled) + } + } + + func resume() async { + guard !Task.isCancelled else { return } + start() + guard let task = startupTask else { return } + await withTaskCancellationHandler { await task.value } onCancel: { task.cancel() } + } + + private func finishStartup(_ requested: TeraSessionGeneration, cancelled: Bool) { + guard generation == requested else { return } + startupTask = nil + if cancelled { + today.stop() + add.suspend() + } + } + + func suspend() { + cancelStartup() + today.stop() + add.suspend() + search.stop() + me.stop() + settings.stop() + media.reset() + } + + func stop() { + cancelStartup() + today.stop() + add.stop() + search.stop() + me.stop() + settings.stop() + media.reset() + } + + private func cancelStartup() { + generation = generation.invalidated() + startupTask?.cancel() + startupTask = nil + } +} diff --git a/TeraTests/TeraProductStartupTests.swift b/TeraTests/TeraProductStartupTests.swift @@ -0,0 +1,166 @@ +@testable import TeraApp +import XCTest + +@MainActor +final class TeraProductStartupTests: XCTestCase { + func testLocalAddIsEditableWhileTodayRefreshHasNotReturned() async throws { + let fixture = try await StartupFixture() + let refresh = await fixture.backend.pause(.refresh) + fixture.stores.start() + await refresh.entered.wait() + await TeraScopeFixtures.eventually { fixture.stores.add.isProductReady } + XCTAssertEqual(Set(fixture.stores.add.schemas.map(\.commandType)), Set(TeraAddCommandType.allCases)) + XCTAssertEqual(fixture.stores.add.drafts.first?.form?.content, "old") + XCTAssertEqual(fixture.stores.today.presentation.refresh, .refreshing) + fixture.stores.add.updateForm(\.content, "Compose while refresh is held") + XCTAssertTrue(fixture.stores.add.canSave) + XCTAssertEqual(fixture.stores.add.form.content, "Compose while refresh is held") + fixture.stores.suspend() + await refresh.resume.open() + try await fixture.close() + } + + func testSuspendCancelsBothStartupReadsBeforeTheirBackendsReturn() async throws { + let fixture = try await StartupFixture() + let page = await fixture.backend.pause(.page) + let drafts = await fixture.backend.pause(.drafts) + let task = Task { await fixture.stores.resume() } + await page.entered.wait() + await drafts.entered.wait() + fixture.stores.suspend() + await task.value + XCTAssertTrue(fixture.stores.today.cards.isEmpty) + XCTAssertTrue(fixture.stores.add.schemas.isEmpty) + XCTAssertTrue(fixture.stores.add.drafts.isEmpty) + XCTAssertFalse(fixture.stores.add.isProductReady) + XCTAssertEqual(fixture.stores.today.observationState, .stopped) + XCTAssertEqual(fixture.stores.add.observationState, .stopped) + await page.resume.open() + await drafts.resume.open() + try await fixture.close() + XCTAssertTrue(fixture.stores.add.schemas.isEmpty) + XCTAssertTrue(fixture.stores.add.drafts.isEmpty) + XCTAssertTrue(fixture.stores.today.cards.isEmpty) + } + + func testCanceledResumeStopsBothObserversAndCanStartAgain() async throws { + let fixture = try await StartupFixture() + let refresh = await fixture.backend.pause(.refresh) + let drafts = await fixture.backend.pause(.drafts) + let task = Task { await fixture.stores.resume() } + await refresh.entered.wait() + await drafts.entered.wait() + task.cancel() + await task.value + XCTAssertEqual(fixture.stores.today.observationState, .stopped) + XCTAssertEqual(fixture.stores.add.observationState, .stopped) + XCTAssertFalse(fixture.stores.add.isProductReady) + await refresh.resume.open() + await drafts.resume.open() + await fixture.releaseObservers() + await fixture.stores.resume() + XCTAssertTrue(fixture.stores.add.isProductReady) + XCTAssertEqual(fixture.stores.add.schemas.count, 5) + try await fixture.close() + } + + func testConcurrentStartsAndResumesKeepOneObserverPerStore() async throws { + let fixture = try await StartupFixture() + let refresh = await fixture.backend.pause(.refresh) + fixture.stores.start() + fixture.stores.start() + let first = Task { await fixture.stores.resume() } + let second = Task { await fixture.stores.resume() } + await refresh.entered.wait() + await TeraScopeFixtures.eventually { fixture.stores.add.isProductReady } + for observer in fixture.observers { + await observer.entered.wait() + } + let counts = await fixture.backend.counts + XCTAssertEqual(counts[.subscribe], 2) + XCTAssertEqual(counts[.refresh], 1) + XCTAssertEqual(counts[.drafts], 1) + await fixture.releaseObservers() + await refresh.resume.open() + await first.value + await second.value + let final = await fixture.backend.counts + XCTAssertEqual(final[.subscribe], 2) + XCTAssertEqual(final[.refresh], 1) + try await fixture.close() + } + + func testReconfigurationRejectsOldStartupAndKeepsTheReplacementOwned() async throws { + for changed in [false, true] { + try await assertReplacement(changed: changed) + } + } + + func testSuspendAndResumePreserveTheUnsentEditingForm() async throws { + let fixture = try await StartupFixture() + await fixture.releaseObservers() + await fixture.stores.resume() + fixture.stores.add.updateForm(\.content, "Unsent local editing") + fixture.stores.suspend() + await fixture.stores.resume() + XCTAssertTrue(fixture.stores.add.isProductReady) + XCTAssertEqual(fixture.stores.add.form.content, "Unsent local editing") + try await fixture.close() + } + + private func assertReplacement(changed: Bool) async throws { + let fixture = try await StartupFixture() + let oldRefresh = await fixture.backend.pause(.refresh) + let oldDrafts = await fixture.backend.pause(.drafts) + let old = Task { await fixture.stores.resume() } + await oldRefresh.entered.wait() + await oldDrafts.entered.wait() + let snapshot = TeraScopeFixtures.snapshot(relay: changed ? "second" : "first") + await fixture.backend.configure(snapshot) + await fixture.backend.setDrafts([TeraScopeFixtures.draft("replacement", revision: 2)]) + fixture.stores.configure(snapshot: snapshot) + let newRefresh = await fixture.backend.pause(.refresh) + let current = Task { await fixture.stores.resume() } + await newRefresh.entered.wait() + await old.value + await TeraScopeFixtures.eventually { fixture.stores.add.isProductReady } + XCTAssertEqual(fixture.stores.add.drafts.first?.form?.content, "replacement") + XCTAssertEqual(fixture.stores.today.selectedContext?.relayURLs, snapshot.relay?.relays.map(\.url)) + fixture.stores.suspend() + await current.value + XCTAssertEqual(fixture.stores.today.observationState, .stopped) + await oldRefresh.resume.open() + await oldDrafts.resume.open() + await newRefresh.resume.open() + try await fixture.close() + XCTAssertEqual(fixture.stores.add.drafts.first?.form?.content, "replacement") + } +} + +@MainActor +private struct StartupFixture { + let backend: TeraScopeBackend + let client: TeraRuntimeClient + let stores: TeraProductStores + let observers: [ResourceTestPause] + + init() async throws { + backend = try TeraScopeBackend() + client = try await TeraScopeFixtures.client(backend) + stores = TeraProductStores(runtimeClient: client) + stores.configure(snapshot: TeraScopeFixtures.snapshot()) + observers = await [backend.pause(.subscribe), backend.pause(.subscribe)] + } + + func releaseObservers() async { + for observer in observers { + await observer.resume.open() + } + } + + func close() async throws { + stores.stop() + await releaseObservers() + _ = try await client.stop() + } +} diff --git a/TeraTests/TeraScopedStoreTests.swift b/TeraTests/TeraScopedStoreTests.swift @@ -97,21 +97,30 @@ final class TeraScopedStoreTests: XCTestCase { _ = try await client.stop() } - func testStoppedProductResumeCannotStartAddAfterLateTodayCompletion() async throws { + func testStoppedProductResumeCannotInstallLateStartupResults() async throws { let backend = try TeraScopeBackend() let client = try await TeraScopeFixtures.client(backend) let stores = TeraProductStores(runtimeClient: client) stores.configure(snapshot: TeraScopeFixtures.snapshot()) + let firstObserver = await backend.pause(.subscribe) + let secondObserver = await backend.pause(.subscribe) + let drafts = await backend.pause(.drafts) let pause = await backend.pause(.page) let old = Task { await stores.resume() } await pause.entered.wait() + await drafts.entered.wait() stores.stop() - await pause.resume.open() await old.value - XCTAssertEqual(stores.add.state, .idle) + XCTAssertFalse(stores.add.isProductReady) + XCTAssertTrue(stores.add.schemas.isEmpty) + XCTAssertTrue(stores.add.drafts.isEmpty) XCTAssertEqual(stores.add.observationState, .stopped) let calls = await backend.counts[.drafts, default: 0] - XCTAssertEqual(calls, 0) + XCTAssertEqual(calls, 1) + await pause.resume.open() + await drafts.resume.open() + await firstObserver.resume.open() + await secondObserver.resume.open() _ = try await client.stop() } } diff --git a/TeraTests/TeraSessionResourceTests.swift b/TeraTests/TeraSessionResourceTests.swift @@ -4,6 +4,54 @@ import RadrootsKit import XCTest final class TeraSessionResourceTests: XCTestCase { + @MainActor + func testAppModelStartsBothProductStoresAfterSessionAuthorization() async throws { + let fixture = try await Fixture() + defer { fixture.state.remove() } + let model = TeraAppModel( + sessionStore: fixture.session, runtimeClient: fixture.client, + lifecycleCoordinator: .disabled() + ) + let stores = try XCTUnwrap(model.productStores) + XCTAssertEqual(stores.add.state, .idle) + XCTAssertNil(stores.today.presentation.readFailure) + await model.start() + if case .running = model.phase {} else { + XCTFail("The authorized session must run") + } + // This session fixture deliberately rejects product queries. Observing both + // typed failures proves launch dispatched them without another resume event. + await TeraScopeFixtures.eventually { + if case .failed = stores.add.state { + return stores.today.presentation.readFailure != nil + } + return false + } + await model.stop() + XCTAssertEqual(stores.today.observationState, .stopped) + XCTAssertEqual(stores.add.observationState, .stopped) + } + + @MainActor + func testAppModelDoesNotStartProductStoresBeforeIdentityAuthorization() async throws { + let fixture = try await Fixture(createIdentity: false) + defer { fixture.state.remove() } + let model = TeraAppModel( + sessionStore: fixture.session, runtimeClient: fixture.client, + lifecycleCoordinator: .disabled() + ) + let stores = try XCTUnwrap(model.productStores) + await model.start() + XCTAssertEqual(model.phase, .identityRequired) + XCTAssertEqual(stores.add.state, .idle) + XCTAssertTrue(stores.add.schemas.isEmpty) + XCTAssertTrue(stores.today.cards.isEmpty) + XCTAssertNil(stores.today.presentation.readFailure) + XCTAssertEqual(stores.today.observationState, .stopped) + XCTAssertEqual(stores.add.observationState, .stopped) + await model.stop() + } + func testStaleStartupSnapshotDoesNotStopNewSharedSession() async throws { let fixture = try await Fixture() defer { fixture.state.remove() } @@ -57,7 +105,7 @@ final class TeraSessionResourceTests: XCTestCase { let client: TeraRuntimeClient let session: TeraSessionStore - init() async throws { + init(createIdentity: Bool = true) async throws { state = try StateFixture() let secure = InMemorySecureStore() let custody = try RadrootsIdentityCustody( @@ -71,8 +119,14 @@ final class TeraSessionResourceTests: XCTestCase { custody: custody, secureStore: secure, servicePrefix: "org.tera.tests.session.\(UUID().uuidString.lowercased())" ) - let created = try await identity.create() - let backend = try ResourceTestBackend(publicKeyHex: XCTUnwrap(created.publicKeyHex)) + let publicKey: String + if createIdentity { + let created = try await identity.create() + publicKey = try XCTUnwrap(created.publicKeyHex) + } else { + publicKey = String(repeating: "ab", count: 32) + } + let backend = ResourceTestBackend(publicKeyHex: publicKey) self.backend = backend let client = TeraRuntimeClient(factory: { _ in await backend.start() }) self.client = client diff --git a/project.yml b/project.yml @@ -63,6 +63,7 @@ targets: - path: TeraTests/TeraTodayStoreTests.swift - path: TeraTests/TeraTodayPresentationTests.swift - path: TeraTests/TeraTodayCacheFirstTests.swift + - path: TeraTests/TeraProductStartupTests.swift - path: TeraTests/TeraAddSchemaFixtures.swift - path: TeraTests/TeraScopeFixtures.swift - path: TeraTests/TeraScopeBackend.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": "6b5ce897d5273290febc8b831663e12dea759cb0084f182ba7949e1c593fc410", - "xcode_project_sha256": "f7204400383da3d0aacd550654a772742352f99942153c3d8c4b88c685f979b3" + "xcode_project_sha256": "06f5970c0e75efb3ef97ad2bf0b3f13aad54b0b99787f24aa44076606fbb778a" }, "disposition": "unsigned", "platforms": [ diff --git a/test-fixtures/swiftlint-maintainability-baseline.v1.json b/test-fixtures/swiftlint-maintainability-baseline.v1.json @@ -1 +1 @@ -[{"text":" init(","violation":{"severity":"error","ruleIdentifier":"function_body_length","reason":"Initializer body should span 50 lines or less excluding comments and whitespace: currently spans 52 lines","location":{"file":"Tera\/App\/TeraAppModel.swift","character":3,"line":83},"ruleName":"Function Body Length","ruleDescription":"Function bodies should not span too many lines"}},{"text":"actor TeraAddMediaCoordinator: TeraAddMediaHandling {","violation":{"severity":"error","ruleIdentifier":"type_body_length","reason":"Actor body should span 250 lines or less excluding comments and whitespace: currently spans 330 lines","location":{"file":"Tera\/Runtime\/TeraAddMediaCoordinator.swift","character":1,"line":51},"ruleName":"Type Body Length","ruleDescription":"Type bodies should not span too many lines"}},{"text":"#endif","violation":{"severity":"error","ruleIdentifier":"file_length","reason":"File should contain 400 lines or less: currently contains 458","location":{"file":"Tera\/Runtime\/TeraAddMediaCoordinator.swift","character":1,"line":458},"ruleName":"File Length","ruleDescription":"Files should not span too many lines."}},{"text":"private final class TeraGeneratedRuntimeBackend: TeraRuntimeBackend, @unchecked Sendable {","violation":{"severity":"error","ruleIdentifier":"type_body_length","reason":"Class body should span 350 lines or less excluding comments and whitespace: currently spans 545 lines","location":{"file":"Tera\/Runtime\/TeraGeneratedRuntimeBackend.swift","character":15,"line":111},"ruleName":"Type Body Length","ruleDescription":"Type bodies should not span too many lines"}},{"text":"}","violation":{"severity":"error","ruleIdentifier":"file_length","reason":"File should contain 1000 lines or less: currently contains 1640","location":{"file":"Tera\/Runtime\/TeraGeneratedRuntimeBackend.swift","character":1,"line":1640},"ruleName":"File Length","ruleDescription":"Files should not span too many lines."}},{"text":"actor TeraRuntimeClient {","violation":{"severity":"error","ruleIdentifier":"type_body_length","reason":"Actor body should span 350 lines or less excluding comments and whitespace: currently spans 787 lines","location":{"file":"Tera\/Runtime\/TeraRuntimeClient.swift","character":1,"line":291},"ruleName":"Type Body Length","ruleDescription":"Type bodies should not span too many lines"}},{"text":" func changes(bufferCapacity: Int = 16) async throws -> AsyncStream<TeraRuntimeChange> {","violation":{"severity":"error","ruleIdentifier":"function_body_length","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 70 lines","location":{"file":"Tera\/Runtime\/TeraRuntimeClient.swift","character":3,"line":703},"ruleName":"Function Body Length","ruleDescription":"Function bodies should not span too many lines"}},{"text":"}","violation":{"severity":"error","ruleIdentifier":"file_length","reason":"File should contain 1000 lines or less: currently contains 1159","location":{"file":"Tera\/Runtime\/TeraRuntimeClient.swift","character":1,"line":1159},"ruleName":"File Length","ruleDescription":"Files should not span too many lines."}},{"text":"}","violation":{"severity":"error","ruleIdentifier":"file_length","reason":"File should contain 1000 lines or less: currently contains 1210","location":{"file":"Tera\/Runtime\/TeraRuntimeModels.swift","character":1,"line":1210},"ruleName":"File Length","ruleDescription":"Files should not span too many lines."}},{"text":"final class TeraAddStore: ObservableObject {","violation":{"severity":"error","ruleIdentifier":"type_body_length","reason":"Class body should span 350 lines or less excluding comments and whitespace: currently spans 731 lines","location":{"file":"Tera\/State\/TeraAddStore.swift","character":7,"line":11},"ruleName":"Type Body Length","ruleDescription":"Type bodies should not span too many lines"}},{"text":" func retract(_ card: TeraTodayCard) async {","violation":{"severity":"error","ruleIdentifier":"function_body_length","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 51 lines","location":{"file":"Tera\/State\/TeraAddStore.swift","character":3,"line":459},"ruleName":"Function Body Length","ruleDescription":"Function bodies should not span too many lines"}},{"text":"}","violation":{"severity":"error","ruleIdentifier":"file_length","reason":"File should contain 400 lines or less: currently contains 796","location":{"file":"Tera\/State\/TeraAddStore.swift","character":1,"line":796},"ruleName":"File Length","ruleDescription":"Files should not span too many lines."}},{"text":"actor TeraConfigurationStore {","violation":{"severity":"error","ruleIdentifier":"type_body_length","reason":"Actor body should span 350 lines or less excluding comments and whitespace: currently spans 468 lines","location":{"file":"Tera\/State\/TeraConfigurationStore.swift","character":1,"line":78},"ruleName":"Type Body Length","ruleDescription":"Type bodies should not span too many lines"}},{"text":" func load() throws -> TeraAppConfiguration {","violation":{"severity":"error","ruleIdentifier":"function_body_length","reason":"Function body should span 100 lines or less excluding comments and whitespace: currently spans 109 lines","location":{"file":"Tera\/State\/TeraConfigurationStore.swift","character":5,"line":148},"ruleName":"Function Body Length","ruleDescription":"Function bodies should not span too many lines"}},{"text":"}","violation":{"severity":"error","ruleIdentifier":"file_length","reason":"File should contain 400 lines or less: currently contains 759","location":{"file":"Tera\/State\/TeraConfigurationStore.swift","character":1,"line":759},"ruleName":"File Length","ruleDescription":"Files should not span too many lines."}},{"text":"actor TeraSessionStore {","violation":{"severity":"error","ruleIdentifier":"type_body_length","reason":"Actor body should span 350 lines or less excluding comments and whitespace: currently spans 390 lines","location":{"file":"Tera\/State\/TeraSessionStore.swift","character":1,"line":39},"ruleName":"Type Body Length","ruleDescription":"Type bodies should not span too many lines"}},{"text":" private func start(acceptingReconfiguration: Bool) async -> TeraSessionPhase {","violation":{"severity":"error","ruleIdentifier":"cyclomatic_complexity","reason":"Function should have complexity 10 or less; currently complexity is 13","location":{"file":"Tera\/State\/TeraSessionStore.swift","character":11,"line":125},"ruleName":"Cyclomatic Complexity","ruleDescription":"Complexity of function bodies should be limited."}},{"text":"}","violation":{"severity":"error","ruleIdentifier":"file_length","reason":"File should contain 400 lines or less: currently contains 452","location":{"file":"Tera\/State\/TeraSessionStore.swift","character":1,"line":452},"ruleName":"File Length","ruleDescription":"Files should not span too many lines."}},{"text":"struct TeraAddView: View {","violation":{"severity":"error","ruleIdentifier":"type_body_length","reason":"Struct body should span 350 lines or less excluding comments and whitespace: currently spans 454 lines","location":{"file":"Tera\/Views\/TeraAddView.swift","character":1,"line":4},"ruleName":"Type Body Length","ruleDescription":"Type bodies should not span too many lines"}},{"text":"}","violation":{"severity":"error","ruleIdentifier":"file_length","reason":"File should contain 400 lines or less: currently contains 560","location":{"file":"Tera\/Views\/TeraAddView.swift","character":1,"line":560},"ruleName":"File Length","ruleDescription":"Files should not span too many lines."}},{"text":"struct TeraSettingsView: View {","violation":{"severity":"error","ruleIdentifier":"type_body_length","reason":"Struct body should span 250 lines or less excluding comments and whitespace: currently spans 259 lines","location":{"file":"Tera\/Views\/TeraSupportingViews.swift","character":1,"line":365},"ruleName":"Type Body Length","ruleDescription":"Type bodies should not span too many lines"}},{"text":"}","violation":{"severity":"error","ruleIdentifier":"file_length","reason":"File should contain 400 lines or less: currently contains 629","location":{"file":"Tera\/Views\/TeraSupportingViews.swift","character":1,"line":629},"ruleName":"File Length","ruleDescription":"Files should not span too many lines."}},{"text":"}","violation":{"severity":"error","ruleIdentifier":"file_length","reason":"File should contain 400 lines or less: currently contains 474","location":{"file":"Tera\/Views\/TeraTodayView.swift","character":1,"line":474},"ruleName":"File Length","ruleDescription":"Files should not span too many lines."}},{"text":"final class TeraAddStoreTests: XCTestCase {","violation":{"severity":"error","ruleIdentifier":"type_body_length","reason":"Class body should span 350 lines or less excluding comments and whitespace: currently spans 665 lines","location":{"file":"TeraTests\/TeraAddStoreTests.swift","character":7,"line":6},"ruleName":"Type Body Length","ruleDescription":"Type bodies should not span too many lines"}},{"text":"private actor AddBackend: TeraRuntimeBackend {","violation":{"severity":"error","ruleIdentifier":"type_body_length","reason":"Actor body should span 350 lines or less excluding comments and whitespace: currently spans 492 lines","location":{"file":"TeraTests\/TeraAddStoreTests.swift","character":9,"line":844},"ruleName":"Type Body Length","ruleDescription":"Type bodies should not span too many lines"}},{"text":" private func makeStatus(","violation":{"severity":"error","ruleIdentifier":"function_parameter_count","reason":"Function should have 5 parameters or less: it currently has 8","location":{"file":"TeraTests\/TeraAddStoreTests.swift","character":11,"line":1280},"ruleName":"Function Parameter Count","ruleDescription":"Number of function parameters should be low."}},{"text":"}","violation":{"severity":"error","ruleIdentifier":"file_length","reason":"File should contain 1000 lines or less: currently contains 1431","location":{"file":"TeraTests\/TeraAddStoreTests.swift","character":1,"line":1431},"ruleName":"File Length","ruleDescription":"Files should not span too many lines."}},{"text":"final class TeraRemoteQualificationTests: XCTestCase {","violation":{"severity":"error","ruleIdentifier":"type_body_length","reason":"Class body should span 250 lines or less excluding comments and whitespace: currently spans 307 lines","location":{"file":"TeraTests\/TeraRemoteQualificationTests.swift","character":7,"line":7},"ruleName":"Type Body Length","ruleDescription":"Type bodies should not span too many lines"}},{"text":" func testQualificationRejectsAmbiguousOrUnsafeHarnessValues() {","violation":{"severity":"error","ruleIdentifier":"function_body_length","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 94 lines","location":{"file":"TeraTests\/TeraRemoteQualificationTests.swift","character":3,"line":45},"ruleName":"Function Body Length","ruleDescription":"Function bodies should not span too many lines"}},{"text":" func testAuthorizationEvidenceIsRedactedEphemeralAndRelaunchCleaned() throws {","violation":{"severity":"error","ruleIdentifier":"function_body_length","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 53 lines","location":{"file":"TeraTests\/TeraRemoteQualificationTests.swift","character":3,"line":212},"ruleName":"Function Body Length","ruleDescription":"Function bodies should not span too many lines"}},{"text":"final class TeraRuntimeClientTests: XCTestCase {","violation":{"severity":"error","ruleIdentifier":"type_body_length","reason":"Class body should span 250 lines or less excluding comments and whitespace: currently spans 298 lines","location":{"file":"TeraTests\/TeraRuntimeClientTests.swift","character":7,"line":4},"ruleName":"Type Body Length","ruleDescription":"Type bodies should not span too many lines"}},{"text":"final class TeraStateMigrationTests: XCTestCase {","violation":{"severity":"error","ruleIdentifier":"type_body_length","reason":"Class body should span 350 lines or less excluding comments and whitespace: currently spans 580 lines","location":{"file":"TeraTests\/TeraStateMigrationTests.swift","character":7,"line":6},"ruleName":"Type Body Length","ruleDescription":"Type bodies should not span too many lines"}},{"text":" func testRelayValidationMatchesRuntimeProfiles() throws {","violation":{"severity":"error","ruleIdentifier":"function_body_length","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 61 lines","location":{"file":"TeraTests\/TeraStateMigrationTests.swift","character":5,"line":7},"ruleName":"Function Body Length","ruleDescription":"Function bodies should not span too many lines"}},{"text":"}","violation":{"severity":"error","ruleIdentifier":"file_length","reason":"File should contain 400 lines or less: currently contains 622","location":{"file":"TeraTests\/TeraStateMigrationTests.swift","character":1,"line":622},"ruleName":"File Length","ruleDescription":"Files should not span too many lines."}},{"text":"private actor SupportingBackend: TeraRuntimeBackend {","violation":{"severity":"error","ruleIdentifier":"type_body_length","reason":"Actor body should span 250 lines or less excluding comments and whitespace: currently spans 253 lines","location":{"file":"TeraTests\/TeraSupportingStoreTests.swift","character":9,"line":207},"ruleName":"Type Body Length","ruleDescription":"Type bodies should not span too many lines"}},{"text":"}","violation":{"severity":"error","ruleIdentifier":"file_length","reason":"File should contain 400 lines or less: currently contains 487","location":{"file":"TeraTests\/TeraSupportingStoreTests.swift","character":1,"line":487},"ruleName":"File Length","ruleDescription":"Files should not span too many lines."}},{"text":"final class TeraRemoteQualificationUITests: XCTestCase {","violation":{"severity":"error","ruleIdentifier":"type_body_length","reason":"Class body should span 350 lines or less excluding comments and whitespace: currently spans 1493 lines","location":{"file":"TeraUITests\/TeraRemoteQualificationUITests.swift","character":7,"line":4},"ruleName":"Type Body Length","ruleDescription":"Type bodies should not span too many lines"}},{"text":" func testLocalSocialFiveFlowScenario() throws {","violation":{"severity":"error","ruleIdentifier":"function_body_length","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 73 lines","location":{"file":"TeraUITests\/TeraRemoteQualificationUITests.swift","character":3,"line":62},"ruleName":"Function Body Length","ruleDescription":"Function bodies should not span too many lines"}},{"text":" func testLocalSocialDeterministicPersonas() throws {","violation":{"severity":"error","ruleIdentifier":"function_body_length","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 66 lines","location":{"file":"TeraUITests\/TeraRemoteQualificationUITests.swift","character":3,"line":174},"ruleName":"Function Body Length","ruleDescription":"Function bodies should not span too many lines"}},{"text":" func testRemoteBlossomUploadAndRecovery() throws {","violation":{"severity":"error","ruleIdentifier":"function_body_length","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 54 lines","location":{"file":"TeraUITests\/TeraRemoteQualificationUITests.swift","character":3,"line":247},"ruleName":"Function Body Length","ruleDescription":"Function bodies should not span too many lines"}},{"text":" private func expectedAddFieldLabel(_ identifier: String, type: String) -> String {","violation":{"severity":"error","ruleIdentifier":"cyclomatic_complexity","reason":"Function should have complexity 10 or less; currently complexity is 18","location":{"file":"TeraUITests\/TeraRemoteQualificationUITests.swift","character":11,"line":573},"ruleName":"Cyclomatic Complexity","ruleDescription":"Complexity of function bodies should be limited."}},{"text":" private func preparePhotoUpdate(_ app: XCUIApplication, marker: String) -> XCUIElement? {","violation":{"severity":"error","ruleIdentifier":"cyclomatic_complexity","reason":"Function should have complexity 10 or less; currently complexity is 11","location":{"file":"TeraUITests\/TeraRemoteQualificationUITests.swift","character":11,"line":767},"ruleName":"Cyclomatic Complexity","ruleDescription":"Complexity of function bodies should be limited."}},{"text":" private func preparePhotoUpdate(_ app: XCUIApplication, marker: String) -> XCUIElement? {","violation":{"severity":"error","ruleIdentifier":"function_body_length","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 66 lines","location":{"file":"TeraUITests\/TeraRemoteQualificationUITests.swift","character":11,"line":767},"ruleName":"Function Body Length","ruleDescription":"Function bodies should not span too many lines"}},{"text":" static func environment(","violation":{"severity":"error","ruleIdentifier":"function_body_length","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 67 lines","location":{"file":"TeraUITests\/TeraRemoteQualificationUITests.swift","character":10,"line":1736},"ruleName":"Function Body Length","ruleDescription":"Function bodies should not span too many lines"}},{"text":"}","violation":{"severity":"error","ruleIdentifier":"file_length","reason":"File should contain 1000 lines or less: currently contains 2212","location":{"file":"TeraUITests\/TeraRemoteQualificationUITests.swift","character":1,"line":2212},"ruleName":"File Length","ruleDescription":"Files should not span too many lines."}}] -\ No newline at end of file +[{"text":" init(","violation":{"severity":"error","ruleIdentifier":"function_body_length","reason":"Initializer body should span 50 lines or less excluding comments and whitespace: currently spans 52 lines","location":{"file":"Tera\/App\/TeraAppModel.swift","character":3,"line":21},"ruleName":"Function Body Length","ruleDescription":"Function bodies should not span too many lines"}},{"text":"actor TeraAddMediaCoordinator: TeraAddMediaHandling {","violation":{"severity":"error","ruleIdentifier":"type_body_length","reason":"Actor body should span 250 lines or less excluding comments and whitespace: currently spans 330 lines","location":{"file":"Tera\/Runtime\/TeraAddMediaCoordinator.swift","character":1,"line":51},"ruleName":"Type Body Length","ruleDescription":"Type bodies should not span too many lines"}},{"text":"#endif","violation":{"severity":"error","ruleIdentifier":"file_length","reason":"File should contain 400 lines or less: currently contains 458","location":{"file":"Tera\/Runtime\/TeraAddMediaCoordinator.swift","character":1,"line":458},"ruleName":"File Length","ruleDescription":"Files should not span too many lines."}},{"text":"private final class TeraGeneratedRuntimeBackend: TeraRuntimeBackend, @unchecked Sendable {","violation":{"severity":"error","ruleIdentifier":"type_body_length","reason":"Class body should span 350 lines or less excluding comments and whitespace: currently spans 545 lines","location":{"file":"Tera\/Runtime\/TeraGeneratedRuntimeBackend.swift","character":15,"line":111},"ruleName":"Type Body Length","ruleDescription":"Type bodies should not span too many lines"}},{"text":"}","violation":{"severity":"error","ruleIdentifier":"file_length","reason":"File should contain 1000 lines or less: currently contains 1640","location":{"file":"Tera\/Runtime\/TeraGeneratedRuntimeBackend.swift","character":1,"line":1640},"ruleName":"File Length","ruleDescription":"Files should not span too many lines."}},{"text":"actor TeraRuntimeClient {","violation":{"severity":"error","ruleIdentifier":"type_body_length","reason":"Actor body should span 350 lines or less excluding comments and whitespace: currently spans 787 lines","location":{"file":"Tera\/Runtime\/TeraRuntimeClient.swift","character":1,"line":291},"ruleName":"Type Body Length","ruleDescription":"Type bodies should not span too many lines"}},{"text":" func changes(bufferCapacity: Int = 16) async throws -> AsyncStream<TeraRuntimeChange> {","violation":{"severity":"error","ruleIdentifier":"function_body_length","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 70 lines","location":{"file":"Tera\/Runtime\/TeraRuntimeClient.swift","character":3,"line":703},"ruleName":"Function Body Length","ruleDescription":"Function bodies should not span too many lines"}},{"text":"}","violation":{"severity":"error","ruleIdentifier":"file_length","reason":"File should contain 1000 lines or less: currently contains 1159","location":{"file":"Tera\/Runtime\/TeraRuntimeClient.swift","character":1,"line":1159},"ruleName":"File Length","ruleDescription":"Files should not span too many lines."}},{"text":"}","violation":{"severity":"error","ruleIdentifier":"file_length","reason":"File should contain 1000 lines or less: currently contains 1210","location":{"file":"Tera\/Runtime\/TeraRuntimeModels.swift","character":1,"line":1210},"ruleName":"File Length","ruleDescription":"Files should not span too many lines."}},{"text":"final class TeraAddStore: ObservableObject {","violation":{"severity":"error","ruleIdentifier":"type_body_length","reason":"Class body should span 350 lines or less excluding comments and whitespace: currently spans 731 lines","location":{"file":"Tera\/State\/TeraAddStore.swift","character":7,"line":11},"ruleName":"Type Body Length","ruleDescription":"Type bodies should not span too many lines"}},{"text":" func retract(_ card: TeraTodayCard) async {","violation":{"severity":"error","ruleIdentifier":"function_body_length","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 51 lines","location":{"file":"Tera\/State\/TeraAddStore.swift","character":3,"line":459},"ruleName":"Function Body Length","ruleDescription":"Function bodies should not span too many lines"}},{"text":"}","violation":{"severity":"error","ruleIdentifier":"file_length","reason":"File should contain 400 lines or less: currently contains 796","location":{"file":"Tera\/State\/TeraAddStore.swift","character":1,"line":796},"ruleName":"File Length","ruleDescription":"Files should not span too many lines."}},{"text":"actor TeraConfigurationStore {","violation":{"severity":"error","ruleIdentifier":"type_body_length","reason":"Actor body should span 350 lines or less excluding comments and whitespace: currently spans 468 lines","location":{"file":"Tera\/State\/TeraConfigurationStore.swift","character":1,"line":78},"ruleName":"Type Body Length","ruleDescription":"Type bodies should not span too many lines"}},{"text":" func load() throws -> TeraAppConfiguration {","violation":{"severity":"error","ruleIdentifier":"function_body_length","reason":"Function body should span 100 lines or less excluding comments and whitespace: currently spans 109 lines","location":{"file":"Tera\/State\/TeraConfigurationStore.swift","character":5,"line":148},"ruleName":"Function Body Length","ruleDescription":"Function bodies should not span too many lines"}},{"text":"}","violation":{"severity":"error","ruleIdentifier":"file_length","reason":"File should contain 400 lines or less: currently contains 759","location":{"file":"Tera\/State\/TeraConfigurationStore.swift","character":1,"line":759},"ruleName":"File Length","ruleDescription":"Files should not span too many lines."}},{"text":"actor TeraSessionStore {","violation":{"severity":"error","ruleIdentifier":"type_body_length","reason":"Actor body should span 350 lines or less excluding comments and whitespace: currently spans 390 lines","location":{"file":"Tera\/State\/TeraSessionStore.swift","character":1,"line":39},"ruleName":"Type Body Length","ruleDescription":"Type bodies should not span too many lines"}},{"text":" private func start(acceptingReconfiguration: Bool) async -> TeraSessionPhase {","violation":{"severity":"error","ruleIdentifier":"cyclomatic_complexity","reason":"Function should have complexity 10 or less; currently complexity is 13","location":{"file":"Tera\/State\/TeraSessionStore.swift","character":11,"line":125},"ruleName":"Cyclomatic Complexity","ruleDescription":"Complexity of function bodies should be limited."}},{"text":"}","violation":{"severity":"error","ruleIdentifier":"file_length","reason":"File should contain 400 lines or less: currently contains 452","location":{"file":"Tera\/State\/TeraSessionStore.swift","character":1,"line":452},"ruleName":"File Length","ruleDescription":"Files should not span too many lines."}},{"text":"struct TeraAddView: View {","violation":{"severity":"error","ruleIdentifier":"type_body_length","reason":"Struct body should span 350 lines or less excluding comments and whitespace: currently spans 454 lines","location":{"file":"Tera\/Views\/TeraAddView.swift","character":1,"line":4},"ruleName":"Type Body Length","ruleDescription":"Type bodies should not span too many lines"}},{"text":"}","violation":{"severity":"error","ruleIdentifier":"file_length","reason":"File should contain 400 lines or less: currently contains 560","location":{"file":"Tera\/Views\/TeraAddView.swift","character":1,"line":560},"ruleName":"File Length","ruleDescription":"Files should not span too many lines."}},{"text":"struct TeraSettingsView: View {","violation":{"severity":"error","ruleIdentifier":"type_body_length","reason":"Struct body should span 250 lines or less excluding comments and whitespace: currently spans 259 lines","location":{"file":"Tera\/Views\/TeraSupportingViews.swift","character":1,"line":365},"ruleName":"Type Body Length","ruleDescription":"Type bodies should not span too many lines"}},{"text":"}","violation":{"severity":"error","ruleIdentifier":"file_length","reason":"File should contain 400 lines or less: currently contains 629","location":{"file":"Tera\/Views\/TeraSupportingViews.swift","character":1,"line":629},"ruleName":"File Length","ruleDescription":"Files should not span too many lines."}},{"text":"}","violation":{"severity":"error","ruleIdentifier":"file_length","reason":"File should contain 400 lines or less: currently contains 474","location":{"file":"Tera\/Views\/TeraTodayView.swift","character":1,"line":474},"ruleName":"File Length","ruleDescription":"Files should not span too many lines."}},{"text":"final class TeraAddStoreTests: XCTestCase {","violation":{"severity":"error","ruleIdentifier":"type_body_length","reason":"Class body should span 350 lines or less excluding comments and whitespace: currently spans 665 lines","location":{"file":"TeraTests\/TeraAddStoreTests.swift","character":7,"line":6},"ruleName":"Type Body Length","ruleDescription":"Type bodies should not span too many lines"}},{"text":"private actor AddBackend: TeraRuntimeBackend {","violation":{"severity":"error","ruleIdentifier":"type_body_length","reason":"Actor body should span 350 lines or less excluding comments and whitespace: currently spans 492 lines","location":{"file":"TeraTests\/TeraAddStoreTests.swift","character":9,"line":844},"ruleName":"Type Body Length","ruleDescription":"Type bodies should not span too many lines"}},{"text":" private func makeStatus(","violation":{"severity":"error","ruleIdentifier":"function_parameter_count","reason":"Function should have 5 parameters or less: it currently has 8","location":{"file":"TeraTests\/TeraAddStoreTests.swift","character":11,"line":1280},"ruleName":"Function Parameter Count","ruleDescription":"Number of function parameters should be low."}},{"text":"}","violation":{"severity":"error","ruleIdentifier":"file_length","reason":"File should contain 1000 lines or less: currently contains 1431","location":{"file":"TeraTests\/TeraAddStoreTests.swift","character":1,"line":1431},"ruleName":"File Length","ruleDescription":"Files should not span too many lines."}},{"text":"final class TeraRemoteQualificationTests: XCTestCase {","violation":{"severity":"error","ruleIdentifier":"type_body_length","reason":"Class body should span 250 lines or less excluding comments and whitespace: currently spans 307 lines","location":{"file":"TeraTests\/TeraRemoteQualificationTests.swift","character":7,"line":7},"ruleName":"Type Body Length","ruleDescription":"Type bodies should not span too many lines"}},{"text":" func testQualificationRejectsAmbiguousOrUnsafeHarnessValues() {","violation":{"severity":"error","ruleIdentifier":"function_body_length","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 94 lines","location":{"file":"TeraTests\/TeraRemoteQualificationTests.swift","character":3,"line":45},"ruleName":"Function Body Length","ruleDescription":"Function bodies should not span too many lines"}},{"text":" func testAuthorizationEvidenceIsRedactedEphemeralAndRelaunchCleaned() throws {","violation":{"severity":"error","ruleIdentifier":"function_body_length","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 53 lines","location":{"file":"TeraTests\/TeraRemoteQualificationTests.swift","character":3,"line":212},"ruleName":"Function Body Length","ruleDescription":"Function bodies should not span too many lines"}},{"text":"final class TeraRuntimeClientTests: XCTestCase {","violation":{"severity":"error","ruleIdentifier":"type_body_length","reason":"Class body should span 250 lines or less excluding comments and whitespace: currently spans 298 lines","location":{"file":"TeraTests\/TeraRuntimeClientTests.swift","character":7,"line":4},"ruleName":"Type Body Length","ruleDescription":"Type bodies should not span too many lines"}},{"text":"final class TeraStateMigrationTests: XCTestCase {","violation":{"severity":"error","ruleIdentifier":"type_body_length","reason":"Class body should span 350 lines or less excluding comments and whitespace: currently spans 580 lines","location":{"file":"TeraTests\/TeraStateMigrationTests.swift","character":7,"line":6},"ruleName":"Type Body Length","ruleDescription":"Type bodies should not span too many lines"}},{"text":" func testRelayValidationMatchesRuntimeProfiles() throws {","violation":{"severity":"error","ruleIdentifier":"function_body_length","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 61 lines","location":{"file":"TeraTests\/TeraStateMigrationTests.swift","character":5,"line":7},"ruleName":"Function Body Length","ruleDescription":"Function bodies should not span too many lines"}},{"text":"}","violation":{"severity":"error","ruleIdentifier":"file_length","reason":"File should contain 400 lines or less: currently contains 622","location":{"file":"TeraTests\/TeraStateMigrationTests.swift","character":1,"line":622},"ruleName":"File Length","ruleDescription":"Files should not span too many lines."}},{"text":"private actor SupportingBackend: TeraRuntimeBackend {","violation":{"severity":"error","ruleIdentifier":"type_body_length","reason":"Actor body should span 250 lines or less excluding comments and whitespace: currently spans 253 lines","location":{"file":"TeraTests\/TeraSupportingStoreTests.swift","character":9,"line":207},"ruleName":"Type Body Length","ruleDescription":"Type bodies should not span too many lines"}},{"text":"}","violation":{"severity":"error","ruleIdentifier":"file_length","reason":"File should contain 400 lines or less: currently contains 487","location":{"file":"TeraTests\/TeraSupportingStoreTests.swift","character":1,"line":487},"ruleName":"File Length","ruleDescription":"Files should not span too many lines."}},{"text":"final class TeraRemoteQualificationUITests: XCTestCase {","violation":{"severity":"error","ruleIdentifier":"type_body_length","reason":"Class body should span 350 lines or less excluding comments and whitespace: currently spans 1493 lines","location":{"file":"TeraUITests\/TeraRemoteQualificationUITests.swift","character":7,"line":4},"ruleName":"Type Body Length","ruleDescription":"Type bodies should not span too many lines"}},{"text":" func testLocalSocialFiveFlowScenario() throws {","violation":{"severity":"error","ruleIdentifier":"function_body_length","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 73 lines","location":{"file":"TeraUITests\/TeraRemoteQualificationUITests.swift","character":3,"line":62},"ruleName":"Function Body Length","ruleDescription":"Function bodies should not span too many lines"}},{"text":" func testLocalSocialDeterministicPersonas() throws {","violation":{"severity":"error","ruleIdentifier":"function_body_length","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 66 lines","location":{"file":"TeraUITests\/TeraRemoteQualificationUITests.swift","character":3,"line":174},"ruleName":"Function Body Length","ruleDescription":"Function bodies should not span too many lines"}},{"text":" func testRemoteBlossomUploadAndRecovery() throws {","violation":{"severity":"error","ruleIdentifier":"function_body_length","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 54 lines","location":{"file":"TeraUITests\/TeraRemoteQualificationUITests.swift","character":3,"line":247},"ruleName":"Function Body Length","ruleDescription":"Function bodies should not span too many lines"}},{"text":" private func expectedAddFieldLabel(_ identifier: String, type: String) -> String {","violation":{"severity":"error","ruleIdentifier":"cyclomatic_complexity","reason":"Function should have complexity 10 or less; currently complexity is 18","location":{"file":"TeraUITests\/TeraRemoteQualificationUITests.swift","character":11,"line":573},"ruleName":"Cyclomatic Complexity","ruleDescription":"Complexity of function bodies should be limited."}},{"text":" private func preparePhotoUpdate(_ app: XCUIApplication, marker: String) -> XCUIElement? {","violation":{"severity":"error","ruleIdentifier":"cyclomatic_complexity","reason":"Function should have complexity 10 or less; currently complexity is 11","location":{"file":"TeraUITests\/TeraRemoteQualificationUITests.swift","character":11,"line":767},"ruleName":"Cyclomatic Complexity","ruleDescription":"Complexity of function bodies should be limited."}},{"text":" private func preparePhotoUpdate(_ app: XCUIApplication, marker: String) -> XCUIElement? {","violation":{"severity":"error","ruleIdentifier":"function_body_length","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 66 lines","location":{"file":"TeraUITests\/TeraRemoteQualificationUITests.swift","character":11,"line":767},"ruleName":"Function Body Length","ruleDescription":"Function bodies should not span too many lines"}},{"text":" static func environment(","violation":{"severity":"error","ruleIdentifier":"function_body_length","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 67 lines","location":{"file":"TeraUITests\/TeraRemoteQualificationUITests.swift","character":10,"line":1736},"ruleName":"Function Body Length","ruleDescription":"Function bodies should not span too many lines"}},{"text":"}","violation":{"severity":"error","ruleIdentifier":"file_length","reason":"File should contain 1000 lines or less: currently contains 2212","location":{"file":"TeraUITests\/TeraRemoteQualificationUITests.swift","character":1,"line":2212},"ruleName":"File Length","ruleDescription":"Files should not span too many lines."}}] +\ No newline at end of file