rename: user-facing product name Shotdeck -> Redline; legacy PDFs still recognized

This commit is contained in:
2026-09-01 22:25:15 +04:00
parent 74606e5046
commit d05bd735b5
15 changed files with 158 additions and 26 deletions
+1 -1
View File
@@ -128,7 +128,7 @@ struct MenuBarView: View {
Button {
NSApp.terminate(nil)
} label: {
actionLabel("Quit Shotdeck")
actionLabel("Quit Redline")
}
}
.buttonStyle(.plain)
+1 -1
View File
@@ -18,7 +18,7 @@ extension AppModel: SendCapable {
let finalURL = outboxDir.appendingPathComponent(fileName)
// Same directory as the final target so the rename below is same-volume (atomic).
let tempURL = outboxDir.appendingPathComponent(".shotdeck-\(UUID().uuidString).pdf")
let title = "Shotdeck \(DubaiTime.stamp(workingSession.createdAt))"
let title = "Redline \(DubaiTime.stamp(workingSession.createdAt))"
do {
// D-13: build off the main actor. Only Sendable values cross into the
+1 -1
View File
@@ -91,7 +91,7 @@ extension AppModel: SettingsWindowPresenting {
}
let hosting = NSHostingController(rootView: SettingsView().environment(self))
let window = NSWindow(contentViewController: hosting)
window.title = "Shotdeck Settings"
window.title = "Redline Settings"
window.styleMask = [.titled, .closable]
window.isReleasedWhenClosed = false
window.center()
+2 -2
View File
@@ -27,7 +27,7 @@ struct ShotdeckApp: App {
Text(count).font(.system(size: 11, weight: .semibold))
}
}
.accessibilityLabel("Shotdeck")
.accessibilityLabel("Redline")
}
.menuBarExtraStyle(.window)
}
@@ -60,7 +60,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
// Safe: temp-dir creation for a path this process controls cannot legitimately fail.
let fallback = try! AppSupportPaths(root: fallbackRoot, outbox: tmp, watchFolder: tmp)
let model = try! makeModel(paths: fallback)
model.setStatus("Shotdeck could not access its storage folder. Captures will not persist.")
model.setStatus("Redline could not access its storage folder. Captures will not persist.")
return model
}
}
@@ -16,7 +16,7 @@ public enum ShotdeckError: Error, LocalizedError, Sendable {
case .screenRecordingNotGranted:
return "Screen Recording is turned off. Grant it in System Settings to capture."
case .noRegionRemembered:
return "No capture region is set. Choose 'Re-select area' from the Shotdeck menu."
return "No capture region is set. Choose 'Re-select area' from the Redline menu."
case .displayNoLongerConnected:
return "The display used for capture is no longer connected."
case .captureFailed(let underlying):
+2 -2
View File
@@ -71,7 +71,7 @@ public struct PDFComposer: Sendable {
}
public static func fileName(for session: CaptureSession) -> String {
"Shotdeck-\(DubaiTime.fileStamp(session.createdAt)).pdf"
"Redline-\(DubaiTime.fileStamp(session.createdAt)).pdf"
}
private static func writePDF(
@@ -86,7 +86,7 @@ public struct PDFComposer: Sendable {
}
let auxiliaryInfo: [String: Any] = [
kCGPDFContextCreator as String: "Shotdeck",
kCGPDFContextCreator as String: "Redline",
kCGPDFContextTitle as String: title,
kCGPDFContextSubject as String: sessionID.uuidString.lowercased(),
]
@@ -93,16 +93,19 @@ public enum AnnotationInspector {
)
}
/// True when this PDF was produced by Shotdeck. Creator attribute is authoritative;
/// True when this PDF was produced by this app. Creator attribute is authoritative;
/// the filename fallback applies ONLY when the creator attribute is absent.
/// Accepts both the current product name ("Redline") and the legacy name ("Shotdeck")
/// so PDFs already on the iPad or in Downloads are still detected.
public static func isShotdeckDocument(_ document: PDFDocument) -> Bool {
if let creator = document.documentAttributes?[PDFDocumentAttribute.creatorAttribute] as? String {
return creator == "Shotdeck" // present creator is authoritative, full stop
// Present creator is authoritative, full stop.
return creator == "Redline" || creator == "Shotdeck"
}
// Creator ABSENT (some apps rewrite metadata on save) -> filename fallback only here.
guard let name = document.documentURL?.lastPathComponent else { return false }
// .lastPathComponent on a file URL is already percent-decoded; do not use .absoluteString.
return name.wholeMatch(of: /^Shotdeck-\d{8}-\d{6}( \d+)?\.pdf$/) != nil
return name.wholeMatch(of: /^(Redline|Shotdeck)-\d{8}-\d{6}( \d+)?\.pdf$/) != nil
// Case-sensitive by construction (Swift Regex literals are case-sensitive by default).
// The optional "( \d+)?" is macOS's duplicate-name suffix AirDrop adds when a file of
// the same name already exists in the watch folder the normal case for a return.
@@ -60,7 +60,7 @@ public actor ReturnWatcher {
}
/// Scans the watch folder once, immediately, without waiting for an event. Every
/// recognized, stable, openable Shotdeck PDF present is (re-)inspected and (re-)recorded
/// recognized, stable, openable Redline/Shotdeck PDF present is (re-)inspected and (re-)recorded
/// into the ledger; returns exactly the documents processed in this call.
@discardableResult
public func scanNow() async throws -> [ReturnedDocument] {