new release script

This commit is contained in:
Mark Gregory
2026-10-18 15:41:16 +01:00
parent 55c4254b25
commit 73e9a3efe2
4 changed files with 157 additions and 30 deletions
+21 -6
View File
@@ -8,10 +8,12 @@ extension Notification.Name {
@main
struct DBSynApp: App {
@StateObject private var model = DatabaseModel()
@AppStorage("preferences.appearance") private var appearance = "system"
var body: some Scene {
WindowGroup("DBSyn") {
WorkbenchLaunchView()
.preferredColorScheme(appearance == "dark" ? .dark : appearance == "light" ? .light : nil)
.onAppear {
if let iconURL = Bundle.main.url(forResource: "DBSyn", withExtension: "icns"),
let icon = NSImage(contentsOf: iconURL) {
@@ -30,7 +32,7 @@ struct DBSynApp: App {
NSApplication.shared.orderFrontStandardAboutPanel(options: [
.applicationName: "DBSyn",
.applicationIcon: icon ?? NSApplication.shared.applicationIconImage,
.version: "Phase 59.2",
.version: "Phase 61",
.credits: NSAttributedString(string: "Native MySQL client for macOS")
])
}
@@ -74,7 +76,7 @@ final class WorkbenchPreferences: ObservableObject {
var alternatingRows = true
@AppStorage("preferences.appearance")
var appearance = "light"
var appearance = "system"
@AppStorage("preferences.autoExpandTables")
var autoExpandTables = true
@@ -112,6 +114,9 @@ struct WorkbenchSettingsView: View {
@AppStorage("preferences.autoExpandTables")
private var autoExpandTables = true
@AppStorage("preferences.appearance")
private var appearance = "system"
var body: some View {
TabView {
@@ -148,7 +153,7 @@ struct WorkbenchSettingsView: View {
}
}
.frame(width: 620, height: 390)
.preferredColorScheme(.light)
.preferredColorScheme(appearance == "dark" ? .dark : appearance == "light" ? .light : nil)
}
private var editorSettings: some View {
@@ -273,6 +278,16 @@ struct WorkbenchSettingsView: View {
private var generalSettings: some View {
settingsPage("General") {
settingsRow("Appearance") {
Picker("Appearance", selection: $appearance) {
Text("System").tag("system")
Text("Light").tag("light")
Text("Dark").tag("dark")
}
.labelsHidden()
.frame(width: 170)
}
settingsRow("Navigator") {
Toggle(
"Automatically expand table folders",
@@ -392,14 +407,14 @@ struct WorkbenchSplashView: View {
.background(
LinearGradient(
colors: [
Color(red: 0.96, green: 0.98, blue: 1),
Color(red: 0.88, green: 0.93, blue: 1)
Color(nsColor: .windowBackgroundColor),
Color(nsColor: .controlBackgroundColor)
],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
)
.preferredColorScheme(.light)
}
private func feature(_ symbol: String, _ title: String) -> some View {
+109 -24
View File
@@ -8,18 +8,10 @@ import UniformTypeIdentifiers
// MARK: - Classic Workbench Colours
private enum WB {
static let toolbar = Color(nsColor: NSColor(
calibratedWhite: 0.88, alpha: 1
))
static let sidebar = Color(nsColor: NSColor(
calibratedWhite: 0.94, alpha: 1
))
static let border = Color(nsColor: NSColor(
calibratedWhite: 0.76, alpha: 1
))
static let header = Color(nsColor: NSColor(
calibratedWhite: 0.91, alpha: 1
))
static let toolbar = Color(nsColor: .controlBackgroundColor)
static let sidebar = Color(nsColor: .underPageBackgroundColor)
static let border = Color(nsColor: .separatorColor)
static let header = Color(nsColor: .controlBackgroundColor)
}
// MARK: - Main Window
@@ -85,11 +77,6 @@ struct MainView: View {
statusBar
}
.frame(minWidth: 800, minHeight: 520)
.preferredColorScheme(
UserDefaults.standard.string(
forKey: "preferences.appearance"
) == "dark" ? .dark : .light
)
.background(Color(nsColor: .windowBackgroundColor))
.onReceive(NotificationCenter.default.publisher(for: .workbenchRevealSQLEditor)) { _ in
administrationPage = nil
@@ -1376,6 +1363,7 @@ struct SQLTextView: NSViewRepresentable {
@Binding var selectedSQL: String
var focusRequest: Int = 0
var completions: [String] = []
@Environment(\.colorScheme) private var colorScheme
func makeCoordinator() -> Coordinator {
Coordinator(self)
@@ -1385,11 +1373,12 @@ struct SQLTextView: NSViewRepresentable {
let scroll = NSTextView.scrollableTextView()
let editor = scroll.documentView as! NSTextView
scroll.appearance = NSAppearance(named: .aqua)
editor.appearance = NSAppearance(named: .aqua)
editor.backgroundColor = .white
editor.textColor = .black
scroll.backgroundColor = .white
let nativeAppearance = NSAppearance(named: colorScheme == .dark ? .darkAqua : .aqua)
scroll.appearance = nativeAppearance
editor.appearance = nativeAppearance
editor.backgroundColor = .textBackgroundColor
editor.textColor = .textColor
scroll.backgroundColor = .textBackgroundColor
editor.isEditable = true
editor.isRichText = false
@@ -1429,6 +1418,13 @@ struct SQLTextView: NSViewRepresentable {
return
}
context.coordinator.parent = self
let nativeAppearance = NSAppearance(named: colorScheme == .dark ? .darkAqua : .aqua)
scroll.appearance = nativeAppearance
editor.appearance = nativeAppearance
editor.backgroundColor = .textBackgroundColor
editor.textColor = .textColor
scroll.backgroundColor = .textBackgroundColor
context.coordinator.highlight(editor)
if context.coordinator.lastFocusRequest != focusRequest {
context.coordinator.lastFocusRequest = focusRequest
DispatchQueue.main.async { [weak editor] in
@@ -2073,6 +2069,7 @@ struct NativeResultGrid: NSViewRepresentable {
let columns: [String]
let rows: [[String]]
var hiddenColumnIndices: Set<Int> = []
@Environment(\.colorScheme) private var colorScheme
func makeCoordinator() -> Coordinator {
Coordinator()
@@ -2084,10 +2081,13 @@ struct NativeResultGrid: NSViewRepresentable {
scroll.hasHorizontalScroller = true
scroll.autohidesScrollers = true
scroll.drawsBackground = true
scroll.backgroundColor = .white
scroll.backgroundColor = .textBackgroundColor
let table = NSTableView()
table.headerView = NSTableHeaderView()
let nativeAppearance = NSAppearance(named: colorScheme == .dark ? .darkAqua : .aqua)
scroll.appearance = nativeAppearance
table.appearance = nativeAppearance
table.usesAlternatingRowBackgroundColors =
UserDefaults.standard.object(
forKey: "preferences.alternatingRows"
@@ -2102,7 +2102,7 @@ struct NativeResultGrid: NSViewRepresentable {
table.allowsColumnResizing = true
table.allowsColumnReordering = true
table.columnAutoresizingStyle = .noColumnAutoresizing
table.backgroundColor = .white
table.backgroundColor = .textBackgroundColor
context.coordinator.columns = columns
context.coordinator.rows = rows
@@ -2126,6 +2126,12 @@ struct NativeResultGrid: NSViewRepresentable {
return
}
let nativeAppearance = NSAppearance(named: colorScheme == .dark ? .darkAqua : .aqua)
scroll.appearance = nativeAppearance
table.appearance = nativeAppearance
scroll.backgroundColor = .textBackgroundColor
table.backgroundColor = .textBackgroundColor
let oldColumns = context.coordinator.columns
context.coordinator.columns = columns
@@ -2963,6 +2969,42 @@ struct WorkbenchDatabaseTransferView: View {
@State private var loadingDatabases = false
@State private var lastBackupURL: URL?
@State private var lastVerificationReport = ""
// Phase 61: Recent backups are kept locally, never uploaded or copied.
@State private var recentBackups: [String] = UserDefaults.standard.stringArray(forKey: "DBSyn.recentBackups.v1") ?? []
@State private var selectedBackupPath = ""
private func rememberBackup(_ url: URL) {
let path = url.standardizedFileURL.path
recentBackups.removeAll { $0 == path }
recentBackups.insert(path, at: 0)
recentBackups = Array(recentBackups.prefix(20))
selectedBackupPath = path
UserDefaults.standard.set(recentBackups, forKey: "DBSyn.recentBackups.v1")
}
private var selectedBackupURL: URL? {
guard !selectedBackupPath.isEmpty,
FileManager.default.fileExists(atPath: selectedBackupPath) else { return nil }
return URL(fileURLWithPath: selectedBackupPath)
}
private func verifyRecentBackup() {
guard let url = selectedBackupURL else { return }
operationTask = Task {
isWorking = true
defer { isWorking = false; operationTask = nil }
do {
try await Task.detached(priority: .utility) {
try WorkbenchBackupIntegrity.verify(url)
}.value
status = "Verified SHA-256: \(url.lastPathComponent)"
progress = 1
} catch {
status = "Verification failed: \(error.localizedDescription)"
}
}
}
private var selectedProfile: ConnectionProfile? {
model.selected
@@ -3075,6 +3117,48 @@ struct WorkbenchDatabaseTransferView: View {
}
}
GroupBox("Recent Backups") {
VStack(alignment: .leading, spacing: 10) {
Text("Previously exported SQL backups. Files stay in their original locations; missing files cannot be verified or restored.")
.font(.caption).foregroundStyle(.secondary)
if recentBackups.isEmpty {
Text("No DBSyn backups recorded yet.").foregroundStyle(.secondary)
} else {
Picker("Backup", selection: $selectedBackupPath) {
Text("Select a backup").tag("")
ForEach(recentBackups, id: \.self) { path in
Text(URL(fileURLWithPath: path).lastPathComponent +
(FileManager.default.fileExists(atPath: path) ? "" : " (missing)"))
.tag(path)
}
}
.labelsHidden()
HStack {
Button("Reveal in Finder") {
if let url = selectedBackupURL {
NSWorkspace.shared.activateFileViewerSelecting([url])
}
}.disabled(selectedBackupURL == nil)
Button("Verify SHA-256") { verifyRecentBackup() }
.disabled(selectedBackupURL == nil || isWorking)
Button("Restore Selected…") {
if let url = selectedBackupURL {
selectedMode = 1
pendingImportURL = url
showingImportConfirmation = true
}
}.disabled(selectedBackupURL == nil || isWorking || database.isEmpty || selectedProfile == nil)
Spacer()
Button("Clear History") {
recentBackups = []
selectedBackupPath = ""
UserDefaults.standard.removeObject(forKey: "DBSyn.recentBackups.v1")
}
}
}
}
}
GroupBox("Progress") {
VStack(alignment: .leading, spacing: 10) {
ProgressView(value: progress)
@@ -3721,6 +3805,7 @@ struct WorkbenchDatabaseTransferView: View {
)
try manifest.write(for: url)
lastBackupURL = url
rememberBackup(url)
progress = 1
status = "Export complete and SHA-256 verified: \(tables.count) tables, \(views.count) views, \(transferredRows.formatted()) rows"
} catch {
+10
View File
@@ -0,0 +1,10 @@
# DBSyn Phase 61.1 — Light and Dark Appearance
- Appearance setting under Settings → General: System, Light, Dark; stored in `preferences.appearance`.
- Appearance selection applies to the application window and preferences; System follows macOS automatically.
- Replaced fixed light-mode toolbar, navigator, header, and border colors with semantic macOS colors.
- Removed light-only overrides from settings and splash screen; splash gradient uses adaptive system colors.
- SQL NSTextView and native NSTableView results use light/dark AppKit appearances and semantic text/background colors, refreshed when appearance changes.
- Existing database, charts, SQL editor, backup catalog and administration features retained.
Validation: Swift syntax parsed using `swiftc -frontend -parse`; full macOS Xcode build and visual testing are not available in this environment.
+17
View File
@@ -0,0 +1,17 @@
# DBSyn Phase 61 — Backup Catalog and Recovery
- Recent SQL backups are recorded automatically after successful export, including SHA-256 checksum and manifest generation.
- Backup catalog remembers up to 20 local file paths across app launches; no backup contents are copied or uploaded.
- Select a backup to reveal it in Finder, verify its SHA-256 sidecar, or initiate restore through the existing confirmation dialog.
- Missing backup files are visibly identified and cannot be selected for verification or restore.
- Clearing the history does not delete backup files.
- Existing export snapshot consistency, backup verification, manifest validation, and import safeguards are retained.
- Includes `scripts/build-run-dbsyn.sh` for the established Debug build-and-launch workflow.
## Caveats
Restores are not transactional. Back up the destination database and verify the chosen backup before restoring. The catalog stores file paths rather than security-scoped bookmarks; files outside permitted locations may require re-selection through the standard file picker.
## Validation
Swift syntax parsed with `swiftc -frontend -parse` where available. Full Xcode build and MySQL integration tests must be run on macOS.