diff --git a/PHASE14-NOTES.md b/PHASE14-NOTES.md new file mode 100644 index 0000000..92ecf40 --- /dev/null +++ b/PHASE14-NOTES.md @@ -0,0 +1,11 @@ +# Phase 14 — MySQL Account Management + +Adds a **Manage Accounts…** sheet under Administration → Users and Privileges. + +Operations: create account, change password, lock/unlock account, grant/revoke an individual database privilege. Every operation displays a SQL preview and requires an explicit confirmation. Passwords are masked in previews, never persisted by the account management form, and cleared after execution. Privileges are chosen from an allowlist. User, host, and database inputs use conservative character allowlists. + +The existing account list and Show Grants remain available. Auto-refresh is switched off when account management opens, and the account list refreshes on dismissal. + +**Limitations:** Requires sufficient MySQL privileges. Only basic ASCII account/host/database names supported. No account deletion, global privileges, roles, or complex grant options. SQL mode NO_BACKSLASH_ESCAPES is rejected for password operations. Password values are sent using the native query protocol, which may expose them in server-side SQL logs; parameterized MySQL account DDL isn't implemented. No automatic rollback for account changes. + +**Validation:** Static/source inspection and ZIP integrity only. This environment cannot compile the macOS Xcode target or verify changes against a MySQL server. Test on a disposable MySQL instance before production use. diff --git a/WorkbenchReplacement/MainView.swift b/WorkbenchReplacement/MainView.swift index e599aae..07bbafb 100644 --- a/WorkbenchReplacement/MainView.swift +++ b/WorkbenchReplacement/MainView.swift @@ -1370,6 +1370,7 @@ struct WorkbenchAdministrationView: View { @State private var grants: [String] = [] @State private var grantsError: String? = nil @State private var showingGrants = false + @State private var showingAccountManager = false private var filteredRows: [[String]] { guard !searchText.isEmpty else { return result.rows } @@ -1434,8 +1435,13 @@ struct WorkbenchAdministrationView: View { Task { await showGrants() } } .disabled(selectedAccount == nil) + Button("Manage Accounts…") { + autoRefresh = false + showingAccountManager = true + } + .disabled(profile == nil) Spacer() - Text("Read-only account inspection") + Text("Changes require confirmation") .foregroundStyle(.secondary) } .padding(.horizontal, 16) @@ -1457,6 +1463,16 @@ struct WorkbenchAdministrationView: View { await refresh() } } + .sheet(isPresented: $showingAccountManager, onDismiss: { + Task { await refresh() } + }) { + if let profile { + WorkbenchAccountManagerView( + profile: profile, + selectedAccount: selectedAccount + ) + } + } .sheet(isPresented: $showingGrants) { VStack(alignment: .leading, spacing: 12) { Text("Grants: \(selectedAccount ?? "")") @@ -2897,3 +2913,183 @@ struct WorkbenchBackupManifest: Codable { return manifest } } + + +// MARK: - Phase 14: Explicitly confirmed MySQL account administration + +private enum WorkbenchAccountAction: String, CaseIterable, Identifiable { + case create = "Create Account" + case password = "Change Password" + case lock = "Lock Account" + case unlock = "Unlock Account" + case grant = "Grant Privileges" + case revoke = "Revoke Privileges" + + var id: String { rawValue } +} + +private struct WorkbenchAccountManagerView: View { + let profile: ConnectionProfile + let selectedAccount: String? + + @Environment(\.dismiss) private var dismiss + @State private var action: WorkbenchAccountAction = .create + @State private var username = "" + @State private var hostname = "%" + @State private var password = "" + @State private var databaseName = "" + @State private var privilege = "SELECT" + @State private var previewSQL = "" + @State private var confirm = false + @State private var running = false + @State private var status: String? + @State private var failure: String? + + private let privileges = ["SELECT", "INSERT", "UPDATE", "DELETE", "CREATE", "ALTER", "DROP", "INDEX", "REFERENCES", "EXECUTE", "CREATE VIEW", "SHOW VIEW", "TRIGGER"] + + var body: some View { + VStack(alignment: .leading, spacing: 16) { + Text("Manage MySQL Accounts").font(.title2.bold()) + Text("Account changes run on \(profile.name). Review the SQL before applying it.") + .foregroundStyle(.secondary) + Form { + Picker("Operation", selection: $action) { + ForEach(WorkbenchAccountAction.allCases) { option in + Text(option.rawValue).tag(option) + } + } + TextField("Username", text: $username) + .textContentType(.username) + TextField("Host", text: $hostname) + if action == .create || action == .password { + SecureField("Password", text: $password) + } + if action == .grant || action == .revoke { + TextField("Database", text: $databaseName) + Picker("Privilege", selection: $privilege) { + ForEach(privileges, id: \.self) { item in + Text(item).tag(item) + } + } + } + } + .formStyle(.grouped) + if let failure { + Text(failure).foregroundStyle(.red).textSelection(.enabled) + } + if let status { + Text(status).foregroundStyle(.green) + } + HStack { + Button("Close") { dismiss() } + Spacer() + Button("Review SQL…") { review() } + .disabled(running) + .keyboardShortcut(.defaultAction) + } + } + .padding(20) + .frame(width: 590) + .onAppear { + if let selectedAccount { + let parts = selectedAccount.split(separator: "\u{001f}", omittingEmptySubsequences: false) + if parts.count == 2 { + username = String(parts[0]) + hostname = String(parts[1]) + } + } + } + .alert("Confirm \(action.rawValue)", isPresented: $confirm) { + Button("Cancel", role: .cancel) { previewSQL = "" } + Button("Apply Change", role: .destructive) { + Task { await execute() } + } + } message: { + // Passwords are never shown in the SQL preview. + Text("Run this operation against \(profile.name)?\n\n\(previewSQL)") + } + } + + // Strict account and database validation keeps identifiers out of SQL syntax. + private func sql() throws -> (command: String, preview: String) { + let allowed = CharacterSet(charactersIn: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.-%") + guard !username.isEmpty, username.unicodeScalars.allSatisfy({ allowed.contains($0) }), + !hostname.isEmpty, hostname.unicodeScalars.allSatisfy({ allowed.contains($0) }) else { + throw AccountError.invalid("Username/host may contain only letters, digits, _, ., -, and %.") + } + let account = "'\(username)'@'\(hostname)'" + switch action { + case .create, .password: + guard !password.isEmpty else { throw AccountError.invalid("Enter a password.") } + // MySQL's default SQL mode interprets backslashes as escapes. + // We check SQL mode before execution to avoid changing password meaning. + let literal = password.replacingOccurrences(of: "\\", with: "\\\\") + .replacingOccurrences(of: "'", with: "''") + let prefix = action == .create ? "CREATE USER" : "ALTER USER" + return ("\(prefix) \(account) IDENTIFIED BY '\(literal)'", + "\(prefix) \(account) IDENTIFIED BY '••••••••'") + case .lock: + return ("ALTER USER \(account) ACCOUNT LOCK", "ALTER USER \(account) ACCOUNT LOCK") + case .unlock: + return ("ALTER USER \(account) ACCOUNT UNLOCK", "ALTER USER \(account) ACCOUNT UNLOCK") + case .grant, .revoke: + guard !databaseName.isEmpty, + databaseName.unicodeScalars.allSatisfy({ allowed.contains($0) }), + privileges.contains(privilege) else { + throw AccountError.invalid("Enter a valid database name and privilege.") + } + let db = "`\(databaseName)`.*" + let command = action == .grant + ? "GRANT \(privilege) ON \(db) TO \(account)" + : "REVOKE \(privilege) ON \(db) FROM \(account)" + return (command, command) + } + } + + private func review() { + failure = nil + status = nil + do { + previewSQL = try sql().preview + confirm = true + } catch { + failure = error.localizedDescription + } + } + + @MainActor + private func execute() async { + running = true + defer { running = false; password = "" } + do { + let command = try sql().command + let credentials = PasswordStore.read(profile.id) + if action == .create || action == .password { + let mode = try await NativeMySQLRunner.run( + profile: profile, database: "", password: credentials, + sql: "SELECT @@SESSION.sql_mode" + ) + let flags = mode.rows.first?.first ?? "" + if flags.split(separator: ",").contains(where: { $0 == "NO_BACKSLASH_ESCAPES" }) { + throw AccountError.invalid("Password changes are disabled for NO_BACKSLASH_ESCAPES SQL mode.") + } + } + _ = try await NativeMySQLRunner.run( + profile: profile, database: "", password: credentials, sql: command + ) + status = "\(action.rawValue) completed successfully." + failure = nil + } catch { + failure = error.localizedDescription + status = nil + } + } + + private enum AccountError: LocalizedError { + case invalid(String) + var errorDescription: String? { + if case let .invalid(message) = self { return message } + return nil + } + } +} diff --git a/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MainView.o b/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MainView.o index 2ff5cca..591d65c 100644 Binary files a/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MainView.o and b/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MainView.o differ diff --git a/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MainView.stringsdata b/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MainView.stringsdata index 8a0a862..b3369c5 100644 --- a/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MainView.stringsdata +++ b/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MainView.stringsdata @@ -247,7 +247,7 @@ "key": "The server did not return any columns.", "location": { "startingColumn": 25, - "startingLine": 1412 + "startingLine": 1413 } }, { @@ -255,7 +255,7 @@ "key": "Loading %@…", "location": { "startingColumn": 30, - "startingLine": 1390 + "startingLine": 1391 } }, { @@ -263,7 +263,7 @@ "key": "No Data", "location": { "startingColumn": 21, - "startingLine": 1409 + "startingLine": 1410 } }, { @@ -271,7 +271,7 @@ "key": "Unable to Load %@", "location": { "startingColumn": 25, - "startingLine": 1396 + "startingLine": 1397 } }, { @@ -279,15 +279,15 @@ "key": "Retry", "location": { "startingColumn": 28, - "startingLine": 1402 + "startingLine": 1403 } }, { "comment": "", - "key": "Read-only account inspection", + "key": "Changes require confirmation", "location": { "startingColumn": 26, - "startingLine": 1438 + "startingLine": 1444 } }, { @@ -295,7 +295,7 @@ "key": "Account", "location": { "startingColumn": 28, - "startingLine": 1422 + "startingLine": 1423 } }, { @@ -303,7 +303,15 @@ "key": "Show Grants", "location": { "startingColumn": 28, - "startingLine": 1433 + "startingLine": 1434 + } + }, + { + "comment": "", + "key": "Manage Accounts…", + "location": { + "startingColumn": 28, + "startingLine": 1438 } }, { @@ -311,7 +319,7 @@ "key": "Select account", "location": { "startingColumn": 30, - "startingLine": 1423 + "startingLine": 1424 } }, { @@ -319,7 +327,7 @@ "key": "%@@%@", "location": { "startingColumn": 38, - "startingLine": 1427 + "startingLine": 1428 } }, { @@ -327,7 +335,7 @@ "key": "Grants: %@", "location": { "startingColumn": 22, - "startingLine": 1462 + "startingLine": 1478 } }, { @@ -335,7 +343,7 @@ "key": "Close", "location": { "startingColumn": 24, - "startingLine": 1474 + "startingLine": 1490 } }, { @@ -343,7 +351,7 @@ "key": "Filter results", "location": { "startingColumn": 27, - "startingLine": 1500 + "startingLine": 1516 } }, { @@ -351,7 +359,7 @@ "key": "Auto-refresh", "location": { "startingColumn": 20, - "startingLine": 1505 + "startingLine": 1521 } }, { @@ -359,7 +367,7 @@ "key": "Refresh every five seconds", "location": { "startingColumn": 23, - "startingLine": 1507 + "startingLine": 1523 } }, { @@ -367,7 +375,7 @@ "key": "Refresh", "location": { "startingColumn": 23, - "startingLine": 1512 + "startingLine": 1528 } }, { @@ -375,7 +383,7 @@ "key": "SQL Editor", "location": { "startingColumn": 23, - "startingLine": 1519 + "startingLine": 1535 } }, { @@ -383,7 +391,7 @@ "key": "Not loaded", "location": { "startingColumn": 22, - "startingLine": 1597 + "startingLine": 1613 } }, { @@ -391,7 +399,7 @@ "key": "Updated %@", "location": { "startingColumn": 21, - "startingLine": 1594 + "startingLine": 1610 } }, { @@ -399,7 +407,7 @@ "key": "%lld rows", "location": { "startingColumn": 18, - "startingLine": 1602 + "startingLine": 1618 } }, { @@ -407,7 +415,7 @@ "key": "Import SQL into %@?", "location": { "startingColumn": 13, - "startingLine": 1904 + "startingLine": 1920 } }, { @@ -415,7 +423,7 @@ "key": "Operation", "location": { "startingColumn": 20, - "startingLine": 1707 + "startingLine": 1723 } }, { @@ -423,7 +431,7 @@ "key": "Data Export", "location": { "startingColumn": 22, - "startingLine": 1708 + "startingLine": 1724 } }, { @@ -431,7 +439,7 @@ "key": "Data Import / Restore", "location": { "startingColumn": 22, - "startingLine": 1709 + "startingLine": 1725 } }, { @@ -439,7 +447,7 @@ "key": "MySQL Connection", "location": { "startingColumn": 26, - "startingLine": 1716 + "startingLine": 1732 } }, { @@ -447,7 +455,7 @@ "key": "Export Options", "location": { "startingColumn": 30, - "startingLine": 1740 + "startingLine": 1756 } }, { @@ -455,7 +463,7 @@ "key": "Import Options", "location": { "startingColumn": 30, - "startingLine": 1796 + "startingLine": 1812 } }, { @@ -463,7 +471,7 @@ "key": "Progress", "location": { "startingColumn": 26, - "startingLine": 1808 + "startingLine": 1824 } }, { @@ -471,7 +479,7 @@ "key": "Connection", "location": { "startingColumn": 36, - "startingLine": 1718 + "startingLine": 1734 } }, { @@ -479,7 +487,7 @@ "key": "Database", "location": { "startingColumn": 28, - "startingLine": 1722 + "startingLine": 1738 } }, { @@ -487,7 +495,7 @@ "key": "Select Database", "location": { "startingColumn": 30, - "startingLine": 1723 + "startingLine": 1739 } }, { @@ -495,7 +503,7 @@ "key": "Refresh Databases", "location": { "startingColumn": 32, - "startingLine": 1731 + "startingLine": 1747 } }, { @@ -503,7 +511,7 @@ "key": "No tables found. Select a database or refresh the list.", "location": { "startingColumn": 34, - "startingLine": 1762 + "startingLine": 1778 } }, { @@ -511,7 +519,7 @@ "key": "InnoDB data is exported using a consistent snapshot and a single streamed query per table. Tables without unique indexes are supported.", "location": { "startingColumn": 30, - "startingLine": 1791 + "startingLine": 1807 } }, { @@ -519,7 +527,7 @@ "key": "Table structure", "location": { "startingColumn": 32, - "startingLine": 1742 + "startingLine": 1758 } }, { @@ -527,7 +535,7 @@ "key": "Table data", "location": { "startingColumn": 32, - "startingLine": 1743 + "startingLine": 1759 } }, { @@ -535,7 +543,7 @@ "key": "Add DROP TABLE IF EXISTS", "location": { "startingColumn": 32, - "startingLine": 1744 + "startingLine": 1760 } }, { @@ -543,7 +551,7 @@ "key": "Include views (definitions)", "location": { "startingColumn": 32, - "startingLine": 1746 + "startingLine": 1762 } }, { @@ -551,7 +559,7 @@ "key": "Include triggers", "location": { "startingColumn": 32, - "startingLine": 1748 + "startingLine": 1764 } }, { @@ -559,7 +567,7 @@ "key": "Include stored procedures and functions", "location": { "startingColumn": 32, - "startingLine": 1750 + "startingLine": 1766 } }, { @@ -567,7 +575,7 @@ "key": "Loading tables…", "location": { "startingColumn": 57, - "startingLine": 1760 + "startingLine": 1776 } }, { @@ -575,7 +583,7 @@ "key": "Tables (%lld selected)", "location": { "startingColumn": 34, - "startingLine": 1753 + "startingLine": 1769 } }, { @@ -583,7 +591,7 @@ "key": "All", "location": { "startingColumn": 36, - "startingLine": 1755 + "startingLine": 1771 } }, { @@ -591,7 +599,7 @@ "key": "None", "location": { "startingColumn": 36, - "startingLine": 1756 + "startingLine": 1772 } }, { @@ -599,7 +607,7 @@ "key": "Refresh", "location": { "startingColumn": 36, - "startingLine": 1757 + "startingLine": 1773 } }, { @@ -607,7 +615,7 @@ "key": "Views (%lld selected)", "location": { "startingColumn": 38, - "startingLine": 1776 + "startingLine": 1792 } }, { @@ -615,7 +623,7 @@ "key": "All", "location": { "startingColumn": 40, - "startingLine": 1778 + "startingLine": 1794 } }, { @@ -623,7 +631,7 @@ "key": "None", "location": { "startingColumn": 40, - "startingLine": 1779 + "startingLine": 1795 } }, { @@ -631,7 +639,7 @@ "key": "Import SQL statements into the selected database.", "location": { "startingColumn": 30, - "startingLine": 1798 + "startingLine": 1814 } }, { @@ -639,7 +647,7 @@ "key": "Supports MySQL DELIMITER directives, triggers and stored routines. Import is not transactional; use a verified backup before restoring.", "location": { "startingColumn": 30, - "startingLine": 1801 + "startingLine": 1817 } }, { @@ -647,7 +655,7 @@ "key": "Current table: %@ / %@ rows · Total exported: %@", "location": { "startingColumn": 30, - "startingLine": 1825 + "startingLine": 1841 } }, { @@ -655,7 +663,7 @@ "key": "Close", "location": { "startingColumn": 24, - "startingLine": 1839 + "startingLine": 1855 } }, { @@ -663,7 +671,7 @@ "key": "Verify Backup…", "location": { "startingColumn": 28, - "startingLine": 1847 + "startingLine": 1863 } }, { @@ -671,7 +679,7 @@ "key": "Verify an SQL file against its SHA-256 checksum sidecar", "location": { "startingColumn": 27, - "startingLine": 1850 + "startingLine": 1866 } }, { @@ -679,7 +687,7 @@ "key": "Verify Restored Database…", "location": { "startingColumn": 28, - "startingLine": 1851 + "startingLine": 1867 } }, { @@ -687,7 +695,7 @@ "key": "Compare restored table row counts and object names with the export manifest", "location": { "startingColumn": 27, - "startingLine": 1855 + "startingLine": 1871 } }, { @@ -695,7 +703,7 @@ "key": "Cancel", "location": { "startingColumn": 28, - "startingLine": 1859 + "startingLine": 1875 } }, { @@ -703,7 +711,7 @@ "key": "Export SQL…", "location": { "startingColumn": 31, - "startingLine": 1866 + "startingLine": 1882 } }, { @@ -711,7 +719,7 @@ "key": "Import SQL…", "location": { "startingColumn": 31, - "startingLine": 1867 + "startingLine": 1883 } }, { @@ -719,7 +727,7 @@ "key": "Import SQL", "location": { "startingColumn": 20, - "startingLine": 1907 + "startingLine": 1923 } }, { @@ -727,7 +735,7 @@ "key": "Cancel", "location": { "startingColumn": 20, - "startingLine": 1915 + "startingLine": 1931 } }, { @@ -735,7 +743,7 @@ "key": "SQL files can modify or delete existing database objects and data. Ensure you have a backup before continuing.", "location": { "startingColumn": 17, - "startingLine": 1920 + "startingLine": 1936 } }, { @@ -743,7 +751,7 @@ "key": "Database Import / Export", "location": { "startingColumn": 22, - "startingLine": 1932 + "startingLine": 1948 } }, { @@ -751,7 +759,119 @@ "key": "Built-in native MySQL database tools", "location": { "startingColumn": 22, - "startingLine": 1935 + "startingLine": 1951 + } + }, + { + "comment": "", + "key": "Confirm %@", + "location": { + "startingColumn": 16, + "startingLine": 3002 + } + }, + { + "comment": "", + "key": "Manage MySQL Accounts", + "location": { + "startingColumn": 18, + "startingLine": 2952 + } + }, + { + "comment": "", + "key": "Account changes run on %@. Review the SQL before applying it.", + "location": { + "startingColumn": 18, + "startingLine": 2953 + } + }, + { + "comment": "", + "key": "Operation", + "location": { + "startingColumn": 24, + "startingLine": 2956 + } + }, + { + "comment": "", + "key": "Username", + "location": { + "startingColumn": 27, + "startingLine": 2961 + } + }, + { + "comment": "", + "key": "Host", + "location": { + "startingColumn": 27, + "startingLine": 2963 + } + }, + { + "comment": "", + "key": "Password", + "location": { + "startingColumn": 33, + "startingLine": 2965 + } + }, + { + "comment": "", + "key": "Database", + "location": { + "startingColumn": 31, + "startingLine": 2968 + } + }, + { + "comment": "", + "key": "Privilege", + "location": { + "startingColumn": 28, + "startingLine": 2969 + } + }, + { + "comment": "", + "key": "Close", + "location": { + "startingColumn": 24, + "startingLine": 2984 + } + }, + { + "comment": "", + "key": "Review SQL…", + "location": { + "startingColumn": 24, + "startingLine": 2986 + } + }, + { + "comment": "", + "key": "Cancel", + "location": { + "startingColumn": 20, + "startingLine": 3003 + } + }, + { + "comment": "", + "key": "Apply Change", + "location": { + "startingColumn": 20, + "startingLine": 3004 + } + }, + { + "comment": "", + "key": "Run this operation against %@?\n\n%@", + "location": { + "startingColumn": 18, + "startingLine": 3009 } } ] diff --git a/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MainView.swiftdeps b/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MainView.swiftdeps index 85e65e9..46a16ad 100644 Binary files a/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MainView.swiftdeps and b/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/MainView.swiftdeps differ diff --git a/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/WorkbenchReplacement-primary.priors b/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/WorkbenchReplacement-primary.priors index c61370f..fa589de 100644 Binary files a/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/WorkbenchReplacement-primary.priors and b/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/WorkbenchReplacement-primary.priors differ diff --git a/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/WorkbenchReplacement.swiftmodule b/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/WorkbenchReplacement.swiftmodule index f42ccd9..947aa20 100644 Binary files a/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/WorkbenchReplacement.swiftmodule and b/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/WorkbenchReplacement.swiftmodule differ diff --git a/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/WorkbenchReplacement.swiftsourceinfo b/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/WorkbenchReplacement.swiftsourceinfo index 17ea5b7..ef20c11 100644 Binary files a/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/WorkbenchReplacement.swiftsourceinfo and b/build/Build/Intermediates.noindex/WorkbenchReplacement.build/Debug/WorkbenchReplacement.build/Objects-normal/arm64/WorkbenchReplacement.swiftsourceinfo differ diff --git a/build/Build/Intermediates.noindex/XCBuildData/build.db b/build/Build/Intermediates.noindex/XCBuildData/build.db index 638f39f..cc28f42 100644 Binary files a/build/Build/Intermediates.noindex/XCBuildData/build.db and b/build/Build/Intermediates.noindex/XCBuildData/build.db differ diff --git a/build/Build/Intermediates.noindex/XCBuildData/registered-launchservices.txt b/build/Build/Intermediates.noindex/XCBuildData/registered-launchservices.txt index abb9e11..fc692d4 100644 --- a/build/Build/Intermediates.noindex/XCBuildData/registered-launchservices.txt +++ b/build/Build/Intermediates.noindex/XCBuildData/registered-launchservices.txt @@ -19,3 +19,4 @@ /Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Products/Debug/WorkbenchReplacement.app /Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Products/Debug/WorkbenchReplacement.app /Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Products/Debug/WorkbenchReplacement.app +/Volumes/Developer/Applications/WorkbenchReplacement/build/Build/Products/Debug/WorkbenchReplacement.app diff --git a/build/Build/Products/Debug/WorkbenchReplacement.app/Contents/MacOS/WorkbenchReplacement b/build/Build/Products/Debug/WorkbenchReplacement.app/Contents/MacOS/WorkbenchReplacement index 1770550..5a1fb03 100755 Binary files a/build/Build/Products/Debug/WorkbenchReplacement.app/Contents/MacOS/WorkbenchReplacement and b/build/Build/Products/Debug/WorkbenchReplacement.app/Contents/MacOS/WorkbenchReplacement differ diff --git a/build/Build/Products/Debug/WorkbenchReplacement.app/Contents/MacOS/WorkbenchReplacement.debug.dylib b/build/Build/Products/Debug/WorkbenchReplacement.app/Contents/MacOS/WorkbenchReplacement.debug.dylib index e33a17e..c2bcd93 100755 Binary files a/build/Build/Products/Debug/WorkbenchReplacement.app/Contents/MacOS/WorkbenchReplacement.debug.dylib and b/build/Build/Products/Debug/WorkbenchReplacement.app/Contents/MacOS/WorkbenchReplacement.debug.dylib differ diff --git a/build/Build/Products/Debug/WorkbenchReplacement.app/Contents/MacOS/__preview.dylib b/build/Build/Products/Debug/WorkbenchReplacement.app/Contents/MacOS/__preview.dylib index b0689d1..43867c2 100755 Binary files a/build/Build/Products/Debug/WorkbenchReplacement.app/Contents/MacOS/__preview.dylib and b/build/Build/Products/Debug/WorkbenchReplacement.app/Contents/MacOS/__preview.dylib differ diff --git a/build/Build/Products/Debug/WorkbenchReplacement.app/Contents/_CodeSignature/CodeResources b/build/Build/Products/Debug/WorkbenchReplacement.app/Contents/_CodeSignature/CodeResources index d908999..6f0c5ab 100644 --- a/build/Build/Products/Debug/WorkbenchReplacement.app/Contents/_CodeSignature/CodeResources +++ b/build/Build/Products/Debug/WorkbenchReplacement.app/Contents/_CodeSignature/CodeResources @@ -19,7 +19,7 @@ cdhash - lXsCGsMbs4xf0Myx5xVMRot9mP8= + AZIB1Fjt6RqKV00p8DK8RGVfw64= requirement identifier "WorkbenchReplacement.debug" and anchor apple generic and certificate leaf[subject.CN] = "Apple Development: Mark Gregory (FLBRJ3BYDD)" and certificate 1[field.1.2.840.113635.100.6.2.1] /* exists */ diff --git a/build/Build/Products/Debug/WorkbenchReplacement.swiftmodule/Project/arm64-apple-macos.swiftsourceinfo b/build/Build/Products/Debug/WorkbenchReplacement.swiftmodule/Project/arm64-apple-macos.swiftsourceinfo index 17ea5b7..ef20c11 100644 Binary files a/build/Build/Products/Debug/WorkbenchReplacement.swiftmodule/Project/arm64-apple-macos.swiftsourceinfo and b/build/Build/Products/Debug/WorkbenchReplacement.swiftmodule/Project/arm64-apple-macos.swiftsourceinfo differ diff --git a/build/Build/Products/Debug/WorkbenchReplacement.swiftmodule/arm64-apple-macos.swiftmodule b/build/Build/Products/Debug/WorkbenchReplacement.swiftmodule/arm64-apple-macos.swiftmodule index f42ccd9..947aa20 100644 Binary files a/build/Build/Products/Debug/WorkbenchReplacement.swiftmodule/arm64-apple-macos.swiftmodule and b/build/Build/Products/Debug/WorkbenchReplacement.swiftmodule/arm64-apple-macos.swiftmodule differ diff --git a/build/Index.noindex/DataStore/v5/units/MainView.o-CNVK7PE7JUX3 b/build/Index.noindex/DataStore/v5/units/MainView.o-CNVK7PE7JUX3 index cd2dd08..d38479d 100644 Binary files a/build/Index.noindex/DataStore/v5/units/MainView.o-CNVK7PE7JUX3 and b/build/Index.noindex/DataStore/v5/units/MainView.o-CNVK7PE7JUX3 differ diff --git a/build/Logs/Build/LogStoreManifest.plist b/build/Logs/Build/LogStoreManifest.plist index dfe04c7..c76e60a 100644 --- a/build/Logs/Build/LogStoreManifest.plist +++ b/build/Logs/Build/LogStoreManifest.plist @@ -48,48 +48,6 @@ uniqueIdentifier 2FC9B29D-EE21-4701-8634-B8C65E3A79A5 - B2C949DC-0503-4157-BC15-F74641625B50 - - className - IDECommandLineBuildLog - documentTypeString - <nil> - domainType - Xcode.IDEActivityLogDomainType.BuildLog - fileName - B2C949DC-0503-4157-BC15-F74641625B50.xcactivitylog - hasPrimaryLog - - primaryObservable - - highLevelStatus - S - totalNumberOfAnalyzerIssues - 0 - totalNumberOfErrors - 0 - totalNumberOfTestFailures - 0 - totalNumberOfWarnings - 0 - - schemeIdentifier-containerName - WorkbenchReplacement project - schemeIdentifier-schemeName - WorkbenchReplacement - schemeIdentifier-sharedScheme - 1 - signature - Building project WorkbenchReplacement with scheme WorkbenchReplacement and configuration Debug - timeStartedRecording - 813877672.07052696 - timeStoppedRecording - 813877685.02004802 - title - Building project WorkbenchReplacement with scheme WorkbenchReplacement and configuration Debug - uniqueIdentifier - B2C949DC-0503-4157-BC15-F74641625B50 - B8940B11-E198-4118-908A-6DCC06A473A1 className @@ -132,6 +90,48 @@ uniqueIdentifier B8940B11-E198-4118-908A-6DCC06A473A1 + BE675D45-226B-48D5-9BED-8FCC12B530FF + + className + IDECommandLineBuildLog + documentTypeString + <nil> + domainType + Xcode.IDEActivityLogDomainType.BuildLog + fileName + BE675D45-226B-48D5-9BED-8FCC12B530FF.xcactivitylog + hasPrimaryLog + + primaryObservable + + highLevelStatus + S + totalNumberOfAnalyzerIssues + 0 + totalNumberOfErrors + 0 + totalNumberOfTestFailures + 0 + totalNumberOfWarnings + 0 + + schemeIdentifier-containerName + WorkbenchReplacement project + schemeIdentifier-schemeName + WorkbenchReplacement + schemeIdentifier-sharedScheme + 1 + signature + Building project WorkbenchReplacement with scheme WorkbenchReplacement and configuration Debug + timeStartedRecording + 813878365.00770497 + timeStoppedRecording + 813878379.89619505 + title + Building project WorkbenchReplacement with scheme WorkbenchReplacement and configuration Debug + uniqueIdentifier + BE675D45-226B-48D5-9BED-8FCC12B530FF + D705A532-F8F7-4B72-94B4-3464B760E3E3 className diff --git a/build/ModuleCache.noindex/Session.modulevalidation b/build/ModuleCache.noindex/Session.modulevalidation index 36a8e94..2c7f2e6 100644 --- a/build/ModuleCache.noindex/Session.modulevalidation +++ b/build/ModuleCache.noindex/Session.modulevalidation @@ -1 +1 @@ -1792185392.568994: Module build session file for module cache at Path(str: "/Volumes/Developer/Applications/WorkbenchReplacement/build/ModuleCache.noindex") +1792185565.3857698: Module build session file for module cache at Path(str: "/Volumes/Developer/Applications/WorkbenchReplacement/build/ModuleCache.noindex") diff --git a/build/SDKStatCaches.noindex/macosx27.0-26A425-e829b21a6f8c9b70876ba0858053c0447231f6df4def33e022ae2eb8f782df1a.sdkstatcache b/build/SDKStatCaches.noindex/macosx27.0-26A425-e829b21a6f8c9b70876ba0858053c0447231f6df4def33e022ae2eb8f782df1a.sdkstatcache index 9a6b86a..e04dccb 100644 Binary files a/build/SDKStatCaches.noindex/macosx27.0-26A425-e829b21a6f8c9b70876ba0858053c0447231f6df4def33e022ae2eb8f782df1a.sdkstatcache and b/build/SDKStatCaches.noindex/macosx27.0-26A425-e829b21a6f8c9b70876ba0858053c0447231f6df4def33e022ae2eb8f782df1a.sdkstatcache differ diff --git a/build/info.plist b/build/info.plist index a61a9ed..7f2e713 100644 --- a/build/info.plist +++ b/build/info.plist @@ -3,7 +3,7 @@ LastAccessedDate - 2026-10-16T21:16:32Z + 2026-10-16T21:19:25Z WorkspacePath /Volumes/Developer/Applications/WorkbenchReplacement/WorkbenchReplacement.xcodeproj