Remove useless code
This commit is contained in:
1 parent
b8a72cc23d
commit
83b54be813
59 files changed
+65
-299
No files matched your search
+2
-1
@@ -1,5 +1,6 @@
|
||||
project: Termind.xcodeproj
|
||||
retain_public: true
|
||||
schemes:
|
||||
- Termind
|
||||
- TFM
|
||||
- TFI
|
||||
- TermindMCPBridge
|
||||
@@ -107,11 +107,6 @@ public final class CommandGeneratorController: CommandGenerating {
|
||||
self.service = CommandGeneratorService()
|
||||
}
|
||||
|
||||
init(ai: AIConfigurationModel, service: CommandGeneratorService) {
|
||||
self.ai = ai
|
||||
self.service = service
|
||||
}
|
||||
|
||||
/// A local readiness check for contextual UI. It never contacts a model;
|
||||
/// it only resolves the configured role and its already local credential.
|
||||
public var isLocallyAvailable: Bool {
|
||||
|
||||
@@ -15,7 +15,7 @@ public nonisolated struct PortForwardingEditorPolicy {
|
||||
public var isWaitingForSave: Bool { isEnabled && !hasSavedConfiguration && !isRuntimeActive }
|
||||
|
||||
public init(
|
||||
rule: PortForwardingRule?, host: Host,
|
||||
rule: PortForwardingRule?,
|
||||
savedRule: PortForwardingRule?, savedHost: Host?,
|
||||
isEnabled: Bool, isRuntimeActive: Bool
|
||||
) {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import Foundation
|
||||
import TermindDomain
|
||||
|
||||
public nonisolated enum RepositoryChangeReason: String, Hashable, Sendable {
|
||||
case initial
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import Foundation
|
||||
import TermindDomain
|
||||
|
||||
public nonisolated struct ReconnectPolicy: Sendable, Equatable {
|
||||
public static let sessionDefault = ReconnectPolicy()
|
||||
|
||||
@@ -197,13 +197,13 @@ public final class SpeechTranscriptionCoordinator {
|
||||
case .partial(let transcript):
|
||||
latestPartial = transcript
|
||||
case .final(let transcript):
|
||||
finish(with: transcript, ownerID: ownerID)
|
||||
finish(with: transcript)
|
||||
case .failure(let failure):
|
||||
fail(failure, for: ownerID)
|
||||
}
|
||||
}
|
||||
|
||||
private func finish(with transcript: String, ownerID: UUID) {
|
||||
private func finish(with transcript: String) {
|
||||
let cleaned = transcript.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
guard !cleaned.isEmpty else {
|
||||
resetSession(cancelService: true)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
@preconcurrency import AVFoundation
|
||||
import TermindApplication
|
||||
|
||||
public typealias SpeechAudioBufferHandler = @Sendable (AVAudioPCMBuffer) -> Void
|
||||
public typealias SpeechAudioLevelHandler = @Sendable (Float) -> Void
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import Foundation
|
||||
import SwiftData
|
||||
import TermindDomain
|
||||
|
||||
@Model
|
||||
public final class PersistentAISecretLocation {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import AgentKit
|
||||
import Foundation
|
||||
import SwiftData
|
||||
import TermindDomain
|
||||
|
||||
@Model
|
||||
public final class PersistentAgentConversation {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import Foundation
|
||||
import SwiftData
|
||||
import TermindApplication
|
||||
import TermindDomain
|
||||
|
||||
public nonisolated struct SettingsRemoteMerge: Sendable {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import AgentKit
|
||||
import Foundation
|
||||
import SwiftData
|
||||
import TermindDomain
|
||||
|
||||
@Model
|
||||
public final class PersistentAgentSkill {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import AgentKit
|
||||
import SwiftUI
|
||||
import TermindApplication
|
||||
|
||||
/// How full the model's context window is, as a ring.
|
||||
///
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import Foundation
|
||||
import TermindApplication
|
||||
|
||||
nonisolated public enum ChatMessageTimestampFormatter {
|
||||
public static func string(
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import SwiftUI
|
||||
import TermindApplication
|
||||
import TermindDomain
|
||||
|
||||
/// A fixed label gutter followed by four equal-width, leading-aligned data columns.
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import Foundation
|
||||
import TermindApplication
|
||||
import TermindInfrastructure
|
||||
|
||||
/// What a command looks like to whoever asked about it.
|
||||
///
|
||||
@@ -75,8 +74,6 @@ public actor AgentBackgroundCommands {
|
||||
public static let defaultIdleTimeout = Duration.seconds(600)
|
||||
|
||||
private struct Entry {
|
||||
let hostID: UUID
|
||||
let command: String
|
||||
let stream: AgentCommandOutputStream
|
||||
let task: Task<SSHExecResult, any Error>
|
||||
let session: any SSHExecSessionProtocol
|
||||
@@ -103,15 +100,12 @@ public actor AgentBackgroundCommands {
|
||||
/// Takes ownership of a command still in flight, and of the session it is
|
||||
/// running on.
|
||||
public func adopt(
|
||||
hostID: UUID, command: String, stream: AgentCommandOutputStream,
|
||||
stream: AgentCommandOutputStream,
|
||||
task: Task<SSHExecResult, any Error>, session: any SSHExecSessionProtocol,
|
||||
now: ContinuousClock.Instant = .now
|
||||
) -> UUID {
|
||||
let handle = UUID()
|
||||
entries[handle] = Entry(
|
||||
hostID: hostID, command: command, stream: stream, task: task,
|
||||
session: session, lastAskedAt: now
|
||||
)
|
||||
entries[handle] = Entry(stream: stream, task: task, session: session, lastAskedAt: now)
|
||||
startSweeping()
|
||||
return handle
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ import AgentKit
|
||||
import Foundation
|
||||
import TermindApplication
|
||||
import TermindDomain
|
||||
import TermindInfrastructure
|
||||
|
||||
import struct TermindDomain.Host
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ import AgentKit
|
||||
import Foundation
|
||||
import TermindApplication
|
||||
import TermindDomain
|
||||
import TermindInfrastructure
|
||||
|
||||
nonisolated public struct ListHostsTool: AgentToolDefinition, AgentToolSchemaBuilding {
|
||||
public static let presenter = AgentToolDetailPresenter(
|
||||
@@ -440,8 +439,7 @@ nonisolated public struct SSHExecTool: AgentToolDefinition, AgentToolSchemaBuild
|
||||
// fresh one.
|
||||
await pool.detachExec(hostID: hostID, session: session)
|
||||
let handle = await background.adopt(
|
||||
hostID: hostID, command: command, stream: stream,
|
||||
task: running, session: session
|
||||
stream: stream, task: running, session: session
|
||||
)
|
||||
let captured = stream.captured
|
||||
return Self.result(
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import Foundation
|
||||
import TermindApplication
|
||||
import TermindInfrastructure
|
||||
|
||||
/// Turns the chunks a running command emits into progress reports.
|
||||
///
|
||||
|
||||
@@ -2,7 +2,6 @@ import AgentKit
|
||||
import Foundation
|
||||
import TermindApplication
|
||||
import TermindDomain
|
||||
import TermindInfrastructure
|
||||
|
||||
import struct TermindDomain.Host
|
||||
|
||||
@@ -306,7 +305,6 @@ public final class AgentHostResolver: Sendable {
|
||||
}
|
||||
|
||||
private struct AuthenticationRequirement {
|
||||
var host: Host
|
||||
var mode: AgentSSHAuthenticationRequest.Mode
|
||||
}
|
||||
|
||||
@@ -321,32 +319,32 @@ public final class AgentHostResolver: Sendable {
|
||||
store.key(keyID)?.requiresPassphraseEntry == true,
|
||||
submission.passphrase?.isEmpty != false
|
||||
{
|
||||
return AuthenticationRequirement(host: host, mode: .passphrase(keyID: keyID))
|
||||
return AuthenticationRequirement(mode: .passphrase(keyID: keyID))
|
||||
}
|
||||
if submission.username.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
|
||||
|| (submission.keyID == nil && submission.password == nil)
|
||||
{
|
||||
return AuthenticationRequirement(host: host, mode: .identity)
|
||||
return AuthenticationRequirement(mode: .identity)
|
||||
}
|
||||
if forcedHostID == host.id {
|
||||
return AuthenticationRequirement(host: host, mode: .identity)
|
||||
return AuthenticationRequirement(mode: .identity)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
guard let identity = store.identity(for: host) else {
|
||||
return AuthenticationRequirement(host: host, mode: .identity)
|
||||
return AuthenticationRequirement(mode: .identity)
|
||||
}
|
||||
let resolution = store.sshCredentials(for: identity)
|
||||
if let keyID = resolution.requiredPassphraseKeyID {
|
||||
return AuthenticationRequirement(host: host, mode: .passphrase(keyID: keyID))
|
||||
return AuthenticationRequirement(mode: .passphrase(keyID: keyID))
|
||||
}
|
||||
if identity.username.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
|
||||
|| resolution.credentials.isEmpty
|
||||
{
|
||||
return AuthenticationRequirement(host: host, mode: .identity)
|
||||
return AuthenticationRequirement(mode: .identity)
|
||||
}
|
||||
if forcedHostID == host.id {
|
||||
return AuthenticationRequirement(host: host, mode: .identity)
|
||||
return AuthenticationRequirement(mode: .identity)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import AgentKit
|
||||
import Foundation
|
||||
import TermindApplication
|
||||
import TermindInfrastructure
|
||||
|
||||
// The two scratch tools that touch a host.
|
||||
//
|
||||
|
||||
@@ -2,7 +2,6 @@ import AgentKit
|
||||
import Foundation
|
||||
import TermindApplication
|
||||
import TermindDomain
|
||||
import TermindInfrastructure
|
||||
|
||||
import struct TermindDomain.Host
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ import AgentKit
|
||||
import Foundation
|
||||
import TermindApplication
|
||||
import TermindDomain
|
||||
import TermindInfrastructure
|
||||
|
||||
/// The wire projection stays with the tool; monitoring has no dependency on AgentKit.
|
||||
nonisolated public enum MetricsToolOutput {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import AgentKit
|
||||
import Foundation
|
||||
import TermindApplication
|
||||
import TermindInfrastructure
|
||||
|
||||
/// What Termind's own agent tools are built from.
|
||||
///
|
||||
|
||||
@@ -2,7 +2,6 @@ import CSSH2
|
||||
import CryptoKit
|
||||
import Foundation
|
||||
import OpenSSL
|
||||
import TermindApplication
|
||||
import TermindDomain
|
||||
|
||||
public nonisolated enum SSHKeyMaterialError: LocalizedError {
|
||||
|
||||
@@ -1325,3 +1325,42 @@ remains behind the context menu and confirmation dialog.
|
||||
**Action:** Move platform-neutral presentation into TermindPresentation, inject tool presenters from the existing registry, and retain native platform rendering and viewport ownership. Replace mobile disclosure and detail branches with shared policy consumers. Estimated duplicate-policy reduction: 150–250 lines, with desktop presentation code moved rather than copied.
|
||||
|
||||
**Verification:** Pending shared, desktop, mobile and bilingual rendering checks.
|
||||
|
||||
## 2026-09-12 — Remove Periphery-confirmed dead code
|
||||
|
||||
**Status:** Completed on 2026-09-12; both app builds, focused tests, formatting, and localization checks pass.
|
||||
|
||||
**Evidence:** Periphery 3.8.0 reported 461 diagnostics while indexing the TFM scheme and 73 while indexing TFI.
|
||||
After excluding test-only diagnostics and reconciling the two platform scans, source-reference checks confirm 34
|
||||
unused production imports, several uncalled private/internal helpers and compatibility accessors, and stored fields
|
||||
that are written but never read. SwiftUI/AppKit lifecycle entry points, Codable payload fields, C callback parameters,
|
||||
protocol witnesses, and fields whose synthesized conformance reads are intentionally excluded from this pass.
|
||||
|
||||
**Action:** Repair the stale Periphery scheme configuration, remove only declarations confirmed by both index data
|
||||
and `rg`, then rerun both app builds, focused tests for changed behavior, strict formatting, localization validation,
|
||||
and Periphery to measure the reduced result set.
|
||||
|
||||
**Estimated reduction:** 70–110 production lines without adding an abstraction.
|
||||
|
||||
**Result:** Removed 34 unused production imports, three empty source files, and confirmed dead initializers,
|
||||
accessors, view helpers, state storage, and background-command metadata. Production code changed by net -150 lines.
|
||||
The TFM production diagnostic count fell from 114 to 69 (461 to 416 including test diagnostics), while the TFI
|
||||
production count fell from 66 to 41. Remaining reports are retained protocol/C callback parameters, synthesized
|
||||
conformance or Codable fields, dynamic UI entry points, and separate candidates that require their own behavioral
|
||||
review. The macOS and iOS builds pass, as do 49 focused Agent background command, port forwarding, and speech tests,
|
||||
strict Swift formatting, Simplified Chinese localization validation, and `git diff --check`.
|
||||
|
||||
## 2026-09-12 — Remove UI implementation-type assertions
|
||||
|
||||
**Status:** Completed.
|
||||
|
||||
**Evidence:** `TranscriptListViewBridgeTests` directly asserts dedicated concrete row classes, the mobile Terminal
|
||||
list suite reflects private SwiftUI/ListViewKit view types, and the macOS Host chooser render test additionally
|
||||
requires an `NSTableView`. Existing behavior tests already cover rendering, row identity and reuse, dynamic type,
|
||||
virtualization, interaction, and state isolation without requiring those implementation choices.
|
||||
|
||||
**Action:** Remove the two tests whose only result is a concrete UI type, drop their test-only helper, and keep the
|
||||
Host chooser environment/rendering coverage without asserting its underlying AppKit table class. Preserve UI type
|
||||
access that is required to exercise or observe actual interaction behavior.
|
||||
|
||||
**Estimated reduction:** 30–40 test lines with no production or public API changes.
|
||||
@@ -1,5 +1,4 @@
|
||||
import GhosttyKit
|
||||
import TermindInfrastructure
|
||||
import TermindRemoteInfrastructure
|
||||
|
||||
nonisolated enum MobileRuntimeVersions {
|
||||
|
||||
@@ -125,8 +125,6 @@ final class MobileAssistantModel {
|
||||
}
|
||||
}
|
||||
|
||||
func stop() { for conversation in store.conversations { conversation.stop() } }
|
||||
|
||||
func suspendRecording() { speech.cancelAll() }
|
||||
|
||||
func expireBackground() {
|
||||
|
||||
@@ -2,7 +2,6 @@ import AgentKit
|
||||
import Foundation
|
||||
import Observation
|
||||
import TermindApplication
|
||||
import TermindDomain
|
||||
|
||||
@MainActor @Observable
|
||||
final class MobileMCPEditorModel {
|
||||
@@ -36,7 +35,6 @@ final class MobileMCPEditorModel {
|
||||
self.discover = discover
|
||||
key.begin(in: ai, reference: server.credentialRef)
|
||||
}
|
||||
var isDirty: Bool { draft != original || key.isDirty }
|
||||
var hasValidURL: Bool {
|
||||
guard let url = URL(string: draft.url.trimmingCharacters(in: .whitespacesAndNewlines)),
|
||||
let scheme = url.scheme?.lowercased(), let host = url.host, !host.isEmpty
|
||||
|
||||
@@ -4,7 +4,6 @@ import SwiftUI
|
||||
import TermindApplication
|
||||
import TermindDomain
|
||||
import TermindPresentation
|
||||
import TermindRemoteInfrastructure
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
private struct MobileConversationAttachmentDropModifier: ViewModifier {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import AgentKit
|
||||
import SwiftUI
|
||||
import TermindApplication
|
||||
import TermindDomain
|
||||
|
||||
enum MobileAssistantLibraryRoute: String, Identifiable, Hashable {
|
||||
case conversations
|
||||
|
||||
@@ -133,7 +133,7 @@ struct MobileHostEditor: View {
|
||||
for rule: PortForwardingRule, runtimeIsActive: Bool
|
||||
) -> PortForwardingEditorPolicy {
|
||||
PortForwardingEditorPolicy(
|
||||
rule: rule, host: draft,
|
||||
rule: rule,
|
||||
savedRule: app.forwardingLibrary.portForwarding(rule.id), savedHost: library.host(draft.id),
|
||||
isEnabled: app.forwardings.isEnabled(rule.id), isRuntimeActive: runtimeIsActive)
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ struct MobileForwardingEditor: View {
|
||||
|
||||
private var state: PortForwardingEditorPolicy {
|
||||
PortForwardingEditorPolicy(
|
||||
rule: draft, host: host,
|
||||
rule: draft,
|
||||
savedRule: app.forwardingLibrary.portForwarding(draft.id), savedHost: app.library.host(host.id),
|
||||
isEnabled: app.forwardings.isEnabled(draft.id), isRuntimeActive: runtime.isActive)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import SwiftUI
|
||||
import TermindDomain
|
||||
import TermindPresentation
|
||||
import TermindRemoteInfrastructure
|
||||
|
||||
struct MobileRemoteFileEditor: View {
|
||||
enum Mode { case rename, permissions }
|
||||
|
||||
@@ -4,7 +4,6 @@ import GhosttyTerminal
|
||||
import Observation
|
||||
import TermindApplication
|
||||
import TermindDomain
|
||||
import TermindInfrastructure
|
||||
import TermindPresentation
|
||||
import TermindRemoteInfrastructure
|
||||
import UIKit
|
||||
@@ -129,7 +128,6 @@ final class MobileWorkspace: Identifiable {
|
||||
private let connection: RemoteSessionConnectionController
|
||||
private var viewport = InMemoryTerminalViewport(columns: 80, rows: 24)
|
||||
|
||||
var uploadConflictCount: Int { pendingUpload?.batch.conflictCount ?? 0 }
|
||||
var transferConflictCount: Int {
|
||||
pendingRemoteCopy?.batch.conflictCount
|
||||
?? pendingDownload?.batch.conflictCount
|
||||
|
||||
@@ -3,7 +3,6 @@ import Foundation
|
||||
nonisolated struct MobileWorkspaceDescriptor: Hashable, Identifiable, Sendable {
|
||||
enum Kind: String, CaseIterable, Sendable {
|
||||
case ssh, sftp
|
||||
var title: String { self == .ssh ? "SSH" : "SFTP" }
|
||||
var symbol: String { self == .ssh ? "terminal" : "folder" }
|
||||
}
|
||||
|
||||
|
||||
@@ -8,26 +8,6 @@ import UIKit
|
||||
|
||||
@Suite(.serialized) @MainActor
|
||||
struct MobileTerminalListTests {
|
||||
@Test func snippetsAndHistoryMountNativeSwiftUILists() async throws {
|
||||
let app = try MobileAppModel(inMemory: true)
|
||||
let host = TermindDomain.Host(name: "Test", host: "example.invalid")
|
||||
app.library.upsert(host)
|
||||
app.library.upsert(Snippet(title: "List files", body: "ls -la\npwd"))
|
||||
app.commandHistory.record("pwd", for: host.id)
|
||||
let workspace = MobileWorkspace(
|
||||
descriptor: MobileWorkspaceDescriptor(hostID: host.id, title: host.name, kind: .ssh),
|
||||
hosts: app.library,
|
||||
commandHistory: app.commandHistory)
|
||||
|
||||
try await withHostedView(MobileTerminalSnippetsView(library: app.library, workspace: workspace)) { root in
|
||||
expectNativeList(in: root.view)
|
||||
}
|
||||
|
||||
try await withHostedView(MobileCommandHistoryView(workspace: workspace)) { root in
|
||||
expectNativeList(in: root.view)
|
||||
}
|
||||
}
|
||||
|
||||
@Test func deletingAnEndpointUpdatesTheNativeHistoryList() async throws {
|
||||
let app = try MobileAppModel(inMemory: true)
|
||||
let host = TermindDomain.Host(name: "Test", host: "example.invalid")
|
||||
@@ -152,15 +132,6 @@ struct MobileTerminalListTests {
|
||||
try await check(controller)
|
||||
}
|
||||
|
||||
private func expectNativeList(in root: UIView) {
|
||||
let legacyList = descendants(of: UIScrollView.self, in: root).first {
|
||||
String(reflecting: type(of: $0)).contains("ListViewKit.ListView")
|
||||
}
|
||||
#expect(legacyList == nil)
|
||||
#expect(descendants(of: MobileHostingListRow.self, in: root).isEmpty)
|
||||
#expect(!nativeVisibleCells(in: root).isEmpty)
|
||||
}
|
||||
|
||||
private func nativeVisibleCells(in root: UIView) -> [UIView] {
|
||||
let collectionCells = descendants(of: UICollectionViewCell.self, in: root)
|
||||
let tableCells = descendants(of: UITableViewCell.self, in: root)
|
||||
|
||||
@@ -60,23 +60,4 @@ nonisolated enum DesktopSettingsKey {
|
||||
}
|
||||
/// The last approval posture selected in the Assistant composer.
|
||||
static let agentPermissionMode = "assistant.permissionMode"
|
||||
static var isWebSearchEnabled: Bool {
|
||||
UserDefaults.standard.object(forKey: SettingsKey.webSearchEnabled) as? Bool
|
||||
?? SettingsKey.defaultWebSearchEnabled
|
||||
}
|
||||
/// Let a provider run the search on its own servers where it can.
|
||||
///
|
||||
/// On by default, because where it works it beats a snippet API: the
|
||||
/// provider reads whole pages and the model simply knows things. Off is the
|
||||
/// compatibility position — every model then goes through the backend
|
||||
/// below, which behaves identically on every endpoint.
|
||||
///
|
||||
/// The switch exists because whether a given endpoint really runs the
|
||||
/// search is a *guess*: `ModelProvider.supportsNativeWebSearch(model:)`
|
||||
/// infers it from the model id, and a gateway can make that inference wrong
|
||||
/// in either direction. This is the escape hatch for when it is.
|
||||
static var isBuiltInWebSearchEnabled: Bool {
|
||||
UserDefaults.standard.object(forKey: SettingsKey.webSearchBuiltIn) as? Bool
|
||||
?? SettingsKey.defaultWebSearchBuiltIn
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import GhosttyKit
|
||||
import TermindInfrastructure
|
||||
import TermindRemoteInfrastructure
|
||||
|
||||
nonisolated enum RuntimeVersions {
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
import TermindApplication
|
||||
@@ -1,5 +1,4 @@
|
||||
import SwiftUI
|
||||
import TermindApplication
|
||||
import TermindDomain
|
||||
|
||||
extension CommandRisk {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import AgentKit
|
||||
import AppKit
|
||||
import Foundation
|
||||
import TermindApplication
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
nonisolated enum ContextAttachmentOpener {
|
||||
|
||||
@@ -3,7 +3,6 @@ import Combine
|
||||
import MarkdownParser
|
||||
import MarkdownView
|
||||
import SwiftUI
|
||||
import TermindApplication
|
||||
import TermindPresentation
|
||||
|
||||
/// The Markdown entry point used by the transcript.
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import SwiftUI
|
||||
import TermindApplication
|
||||
import TermindDomain
|
||||
|
||||
/// The one fenced block Termind owns instead of MarkdownView: a shell command
|
||||
|
||||
@@ -22,7 +22,7 @@ struct PortForwardingEditorState {
|
||||
isEnabled: Bool, runtime: PortForwardingRuntimeState
|
||||
) {
|
||||
let shared = PortForwardingEditorPolicy(
|
||||
rule: rule, host: host, savedRule: savedRule, savedHost: savedHost,
|
||||
rule: rule, savedRule: savedRule, savedHost: savedHost,
|
||||
isEnabled: isEnabled, isRuntimeActive: runtime.isActive)
|
||||
hasSavedConfiguration = shared.hasSavedConfiguration
|
||||
self.isEnabled = shared.isEnabled
|
||||
|
||||
@@ -158,27 +158,10 @@ struct ModelProviderEditorPage: View {
|
||||
app.aiConfiguration.upsert(provider)
|
||||
}
|
||||
|
||||
// MARK: - Binding
|
||||
|
||||
private var endpoint: ProviderEndpoint {
|
||||
app.aiConfiguration.endpoint(for: modelProviderID)
|
||||
}
|
||||
|
||||
/// A two-way binding into the endpoint, written through the store's one
|
||||
/// derivation so the composed URL and the API format are updated together.
|
||||
private func endpointBinding<Value>(
|
||||
_ keyPath: WritableKeyPath<ProviderEndpoint, Value>
|
||||
) -> Binding<Value> {
|
||||
Binding(
|
||||
get: { endpoint[keyPath: keyPath] },
|
||||
set: { newValue in
|
||||
var updated = endpoint
|
||||
updated[keyPath: keyPath] = newValue
|
||||
app.aiConfiguration.setEndpoint(updated, for: modelProviderID)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/// A two-way binding into the stored provider.
|
||||
///
|
||||
/// Reads and writes go through `AIConfigurationModel` rather than through a local copy,
|
||||
|
||||
@@ -638,8 +638,6 @@ private struct TerminalSettings: View {
|
||||
@Environment(AppModel.self) private var app
|
||||
private var fontSize: Double { app.terminalSettings.preferences.fontSize }
|
||||
private var fontFollowTheme: Bool { app.terminalSettings.preferences.fontFollowTheme }
|
||||
private var shellIntegration: Bool { app.terminalSettings.preferences.shellIntegration }
|
||||
private var remoteWindowTitleInMenuBar: Bool { app.terminalSettings.preferences.remoteWindowTitleInMenuBar }
|
||||
private var darkTheme: String { app.terminalSettings.preferences.darkTheme }
|
||||
private var lightTheme: String { app.terminalSettings.preferences.lightTheme }
|
||||
private var darkCustomEnabled: Bool { app.terminalSettings.preferences.darkCustomEnabled }
|
||||
|
||||
@@ -9,13 +9,6 @@ nonisolated enum TerminalCustomConfigurationScheme: String, Identifiable, Hashab
|
||||
|
||||
var id: String { rawValue }
|
||||
|
||||
var settingsKey: String {
|
||||
switch self {
|
||||
case .light: SettingsKey.terminalCustomConfigurationLight
|
||||
case .dark: SettingsKey.terminalCustomConfigurationDark
|
||||
}
|
||||
}
|
||||
|
||||
var preferenceKeyPath: WritableKeyPath<TerminalPreferences, String> {
|
||||
switch self {
|
||||
case .light: \.lightCustomConfiguration
|
||||
|
||||
@@ -242,10 +242,6 @@ struct StatusBarView: View {
|
||||
|
||||
private var host: Host? { pane?.hostID.flatMap { app.hostLibrary.host($0) } }
|
||||
|
||||
private func item(_ symbol: String, _ text: String) -> some View {
|
||||
Label(text, systemImage: symbol).truncationMode(.head)
|
||||
}
|
||||
|
||||
private func workingDirectoryButton(_ path: String, displayed: String) -> some View {
|
||||
Button {
|
||||
app.onboarding.recordUse(of: .statusBarPath)
|
||||
|
||||
@@ -2,10 +2,6 @@ import SwiftData
|
||||
|
||||
@MainActor
|
||||
extension ModelContext {
|
||||
func savePendingChanges() throws {
|
||||
try commit {}
|
||||
}
|
||||
|
||||
func commit(_ operation: () throws -> Void) throws {
|
||||
do {
|
||||
try operation()
|
||||
|
||||
@@ -64,7 +64,6 @@ actor LoopbackHTTPServer {
|
||||
private var handler: Handler?
|
||||
private var stateHandler: StateHandler?
|
||||
private var readyListeners = 0
|
||||
private(set) var state: State = .stopped
|
||||
|
||||
func start(
|
||||
port: UInt16, token: String,
|
||||
@@ -78,7 +77,6 @@ actor LoopbackHTTPServer {
|
||||
self.token = token
|
||||
self.handler = handler
|
||||
self.stateHandler = stateHandler
|
||||
state = .starting
|
||||
stateHandler(.starting)
|
||||
|
||||
do {
|
||||
@@ -125,7 +123,6 @@ actor LoopbackHTTPServer {
|
||||
connections.removeAll()
|
||||
handler = nil
|
||||
readyListeners = 0
|
||||
state = .stopped
|
||||
stateHandler?(.stopped)
|
||||
stateHandler = nil
|
||||
}
|
||||
@@ -135,11 +132,9 @@ actor LoopbackHTTPServer {
|
||||
case .ready:
|
||||
readyListeners += 1
|
||||
if readyListeners == listeners.count {
|
||||
state = .ready(port: port)
|
||||
stateHandler?(.ready(port: port))
|
||||
}
|
||||
case .failed(let error):
|
||||
state = .failed(error.localizedDescription)
|
||||
stateHandler?(.failed(error.localizedDescription))
|
||||
for listener in listeners { listener.cancel() }
|
||||
default:
|
||||
|
||||
@@ -31,15 +31,3 @@ extension AIConfigurationModel {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
extension SkillLibraryModel {
|
||||
convenience init(
|
||||
modelContext: ModelContext,
|
||||
seedDefaults: UserDefaults = .standard
|
||||
) {
|
||||
self.init(
|
||||
persistence: SkillPersistence(context: modelContext),
|
||||
seedDefaults: seedDefaults
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import Foundation
|
||||
|
||||
extension DispatchQueue {
|
||||
func perform<T: Sendable>(
|
||||
_ operation: @escaping @Sendable () throws -> T
|
||||
) async throws -> T {
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
async {
|
||||
do { continuation.resume(returning: try operation()) } catch { continuation.resume(throwing: error) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func perform(_ operation: @escaping @Sendable () -> Void) async {
|
||||
await withCheckedContinuation { continuation in
|
||||
async {
|
||||
operation()
|
||||
continuation.resume()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
import SwiftData
|
||||
import TermindPersistence
|
||||
import TermindRemoteInfrastructure
|
||||
|
||||
extension AISecretStore {
|
||||
convenience init(context: ModelContext) { self.init(context: context, keychain: KeychainSecretStore()) }
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
import TermindInfrastructure
|
||||
import TermindRemoteInfrastructure
|
||||
|
||||
extension KeychainSecretStore {
|
||||
|
||||
@@ -103,7 +103,7 @@ struct AgentBackgroundCommandTests {
|
||||
let commands = AgentBackgroundCommands(sweepInterval: nil)
|
||||
let session = StubSession()
|
||||
let handle = await commands.adopt(
|
||||
hostID: UUID(), command: "make", stream: AgentCommandOutputStream { _, _ in },
|
||||
stream: AgentCommandOutputStream { _, _ in },
|
||||
task: Task { exited(0, stdout: "built") }, session: session
|
||||
)
|
||||
|
||||
@@ -126,7 +126,7 @@ struct AgentBackgroundCommandTests {
|
||||
let released = AsyncRelease()
|
||||
let stream = AgentCommandOutputStream(interval: .zero) { _, _ in }
|
||||
let handle = await commands.adopt(
|
||||
hostID: UUID(), command: "deploy", stream: stream,
|
||||
stream: stream,
|
||||
task: Task {
|
||||
await released.wait()
|
||||
return exited(0)
|
||||
@@ -160,7 +160,7 @@ struct AgentBackgroundCommandTests {
|
||||
let released = AsyncRelease()
|
||||
let stream = AgentCommandOutputStream(interval: .zero) { _, _ in }
|
||||
let handle = await commands.adopt(
|
||||
hostID: UUID(), command: "deploy", stream: stream,
|
||||
stream: stream,
|
||||
task: Task {
|
||||
await released.wait()
|
||||
return exited(0)
|
||||
@@ -184,7 +184,7 @@ struct AgentBackgroundCommandTests {
|
||||
let released = AsyncRelease()
|
||||
let start = ContinuousClock.now
|
||||
let stale = await commands.adopt(
|
||||
hostID: UUID(), command: "tail -f", stream: AgentCommandOutputStream { _, _ in },
|
||||
stream: AgentCommandOutputStream { _, _ in },
|
||||
task: Task {
|
||||
await released.wait()
|
||||
return exited(0)
|
||||
@@ -192,7 +192,7 @@ struct AgentBackgroundCommandTests {
|
||||
now: start
|
||||
)
|
||||
_ = await commands.adopt(
|
||||
hostID: UUID(), command: "deploy", stream: AgentCommandOutputStream { _, _ in },
|
||||
stream: AgentCommandOutputStream { _, _ in },
|
||||
task: Task {
|
||||
await released.wait()
|
||||
return exited(0)
|
||||
@@ -221,7 +221,7 @@ struct AgentBackgroundCommandTests {
|
||||
let session = StubSession()
|
||||
let released = AsyncRelease()
|
||||
_ = await commands.adopt(
|
||||
hostID: UUID(), command: "tail -f", stream: AgentCommandOutputStream { _, _ in },
|
||||
stream: AgentCommandOutputStream { _, _ in },
|
||||
task: Task {
|
||||
await released.wait()
|
||||
return exited(0)
|
||||
@@ -253,7 +253,7 @@ struct AgentBackgroundCommandTests {
|
||||
return exited(0)
|
||||
}
|
||||
let handle = await commands.adopt(
|
||||
hostID: UUID(), command: "deploy", stream: AgentCommandOutputStream { _, _ in },
|
||||
stream: AgentCommandOutputStream { _, _ in },
|
||||
task: task, session: StubSession()
|
||||
)
|
||||
|
||||
|
||||
@@ -511,85 +511,6 @@ struct ArchitectureBoundaryTests {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
func mobileNavigationAndMetricsShareTheDesktopDesign() throws {
|
||||
let root = try String(
|
||||
contentsOf: sourceRoot.appending(path: "../TFI/App/MobileRootView.swift"), encoding: .utf8)
|
||||
#expect(!root.contains("Welcome"))
|
||||
#expect(root.contains("Label(\"Transfers\", systemImage: \"arrow.up.arrow.down\")"))
|
||||
let vault = try String(
|
||||
contentsOf: sourceRoot.appending(path: "../TFI/Features/Vault/MobileVaultView.swift"), encoding: .utf8)
|
||||
let navigation = try String(
|
||||
contentsOf: sourceRoot.appending(path: "../TFI/Features/Shell/MobileNavigation.swift"), encoding: .utf8)
|
||||
#expect(navigation.contains("case vault, metrics, workspaces, assistant, settings"))
|
||||
#expect(!vault.contains("Label(\"Port Forwarding\", systemImage:"))
|
||||
#expect(vault.contains("open(host.id, .ssh)"))
|
||||
let cards = try String(
|
||||
contentsOf: sourceRoot.appending(path: "../TFI/UI/Managed/MobileCards.swift"), encoding: .utf8)
|
||||
#expect(vault.contains("MobileActionCard {"))
|
||||
#expect(cards.contains(".contextMenu {"))
|
||||
#expect(cards.contains("actions"))
|
||||
#expect(!vault.contains("Import SSH Files"))
|
||||
let keys = try String(
|
||||
contentsOf: sourceRoot.appending(path: "../TFI/Features/Keychain/MobileCredentialsView.swift"),
|
||||
encoding: .utf8)
|
||||
#expect(!keys.contains("draft.notes"))
|
||||
let settings = try String(
|
||||
contentsOf: sourceRoot.appending(path: "../TFI/Features/Settings/MobileSettingsView.swift"), encoding: .utf8
|
||||
)
|
||||
#expect(settings.contains("Label(\"App\", systemImage: \"app.badge\")"))
|
||||
#expect(settings.contains("Label(\"Sync\", systemImage: \"icloud\")"))
|
||||
#expect(!settings.contains("Live Activities"))
|
||||
let mobile = try String(
|
||||
contentsOf: sourceRoot.appending(path: "../TFI/Application/Metrics/MobileMetricsModel.swift"),
|
||||
encoding: .utf8)
|
||||
#expect(mobile.contains("ServerMetricsModel("))
|
||||
#expect(!mobile.contains("while !Task.isCancelled"))
|
||||
let workspace = try String(
|
||||
contentsOf: sourceRoot.appending(path: "../TFI/Features/Workspaces/MobileWorkspaceView.swift"),
|
||||
encoding: .utf8)
|
||||
#expect(workspace.contains(".toolbarTitleMenu"))
|
||||
#expect(!workspace.contains("connectionMenuAction"))
|
||||
#expect(workspace.contains("let closeWorkspace: (UUID) -> Void"))
|
||||
#expect(workspace.contains("closeWorkspace(workspace.id)"))
|
||||
#expect(workspace.contains("MobileOverflowMenuLabel(\"More\")"))
|
||||
#expect(workspace.contains("Button(\"Generate Command\", systemImage: \"sparkles\")"))
|
||||
let snippets = try #require(workspace.range(of: "Button(\"Snippets\", systemImage: \"curlybraces\")"))
|
||||
let history = try #require(workspace.range(of: "Button(\"History\", systemImage: \"clock\")"))
|
||||
let historyDivider = try #require(workspace[history.upperBound...].range(of: "Divider()"))
|
||||
let nextMatch = try #require(workspace.range(of: "Button(\"Next Match\", systemImage: \"chevron.down\")"))
|
||||
#expect(snippets.lowerBound < history.lowerBound)
|
||||
#expect(history.lowerBound < historyDivider.lowerBound)
|
||||
#expect(historyDivider.lowerBound < nextMatch.lowerBound)
|
||||
#expect(workspace.contains(".dropDestination(for: URL.self)"))
|
||||
#expect(workspace.contains(".toolbar(.hidden, for: .tabBar)"))
|
||||
#expect(workspace.contains("guard let text = UIPasteboard.general.string else { return }"))
|
||||
#expect(workspace.contains("workspace.insertTerminalText(text)"))
|
||||
let mobileScene = try String(
|
||||
contentsOf: sourceRoot.appending(path: "../TFI/App/MobileSceneModel.swift"), encoding: .utf8)
|
||||
let mobileCommands = try String(
|
||||
contentsOf: sourceRoot.appending(path: "../TFI/App/MobileCommands.swift"), encoding: .utf8)
|
||||
#expect(root.contains(".focusedSceneValue(\\.mobileScene, scene)"))
|
||||
#expect(mobileScene.contains("func closeWorkspace(_ id: MobileWorkspace.ID)"))
|
||||
#expect(mobileCommands.contains("@FocusedValue(\\.mobileScene)"))
|
||||
#expect(!workspace.contains("Circle().fill(workspace.connected"))
|
||||
let keyboard = try String(
|
||||
contentsOf: sourceRoot.appending(path: "../TFI/Features/Terminal/MobileTerminalKeyboard.swift"),
|
||||
encoding: .utf8)
|
||||
#expect(keyboard.contains("ScrollView(.horizontal"))
|
||||
let pasteButton = try #require(keyboard.range(of: "pasteButton"))
|
||||
let keyboardToggle = try #require(keyboard.range(of: "keyboardToggle"))
|
||||
#expect(pasteButton.lowerBound < keyboardToggle.lowerBound)
|
||||
#expect(keyboard.contains("Button(action: paste)"))
|
||||
#expect(keyboard.contains("Label(\"Paste\", systemImage: \"doc.on.clipboard\")"))
|
||||
#expect(keyboard.contains(".padding(.horizontal, 8)"))
|
||||
#expect(keyboard.contains("static let barHeight: CGFloat = 44"))
|
||||
#expect(keyboard.contains(".buttonStyle(.plain)"))
|
||||
#expect(!keyboard.contains(".buttonStyle(.bordered)"))
|
||||
#expect(keyboard.contains(".frame(width: 44, height: 44)"))
|
||||
#expect(keyboard.contains(".frame(height: Self.barHeight)"))
|
||||
}
|
||||
|
||||
@Test
|
||||
func mobilePortUsesSharedStoresAndSeparateSigningFiles() throws {
|
||||
let mobile = try String(
|
||||
|
||||
@@ -47,7 +47,7 @@ struct MetricsInteractionTests {
|
||||
}
|
||||
|
||||
@Test(arguments: [HostChooserPurpose.connect, .batchStartup, .metrics])
|
||||
func hostChoosersMountNativeListsWithTheirRequiredEnvironment(
|
||||
func hostChoosersRenderWithTheirRequiredEnvironment(
|
||||
purpose: HostChooserPurpose
|
||||
) async throws {
|
||||
let host = Host(name: "Fixture", host: "fixture.invalid")
|
||||
@@ -68,7 +68,6 @@ struct MetricsInteractionTests {
|
||||
try await Task.sleep(for: .milliseconds(100))
|
||||
hosting.layoutSubtreeIfNeeded()
|
||||
#expect(hosting.fittingSize.height > 0)
|
||||
#expect(hasTable(hosting))
|
||||
#expect(windowModel.workspaces.isEmpty)
|
||||
#expect(app.serverMetrics.servers.isEmpty)
|
||||
}
|
||||
|
||||
@@ -352,21 +352,6 @@ struct TranscriptListViewBridgeTests {
|
||||
#expect(!presentation.disclosures.hasPendingAutoCollapse(id))
|
||||
}
|
||||
|
||||
@Test
|
||||
func entryKindsUseDedicatedReusableRowTypes() {
|
||||
let coordinator = TranscriptListView.Coordinator()
|
||||
let listView = coordinator.makeListView()
|
||||
let empty = TranscriptEntry(id: UUID(), content: .empty)
|
||||
let assistant = assistant(id: UUID(), text: "Hello")
|
||||
listView.frame = CGRect(x: 0, y: 0, width: 420, height: 800)
|
||||
|
||||
update(coordinator, listView, entries: [empty, assistant])
|
||||
listView.layoutSubtreeIfNeeded()
|
||||
|
||||
#expect(listView.rowView(for: empty.id) is TranscriptEmptyRow)
|
||||
#expect(listView.rowView(for: assistant.id) is TranscriptAssistantRow)
|
||||
}
|
||||
|
||||
@Test
|
||||
func streamingUpdateKeepsTheMountedAssistantRowIdentity() throws {
|
||||
let coordinator = TranscriptListView.Coordinator()
|
||||
|
||||
@@ -10,7 +10,6 @@ nonisolated struct WorkspaceActivityAttributes: ActivityAttributes {
|
||||
var connected: Bool
|
||||
var iconAsset: String? = nil
|
||||
|
||||
var symbol: String { "server.rack" }
|
||||
var destinationURL: URL { WorkspaceActivityAttributes.destinationURL(for: id) }
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user