Zwischenablage, Notizen und der Gesichtsvergleich
Drei neue Module nach dem Bauplan von `ShelfProvider`: reine Regeln getrennt vom Zustand, versionierte Indexdatei, Inhalte in Dateien statt in `UserDefaults` — was dort steht, liest jedes Programm mit `defaults read` mit. Die Zwischenablage achtet `ConcealedType`, `TransientType` und `AutoGeneratedType`. Diese Marken sind eine Verabredung zwischen Programmen und kein Systemdienst; wer sie missachtet, sammelt fremde Passwörter ein. Der Gesichtsvergleich stand im Plan als der eine echte Unsicherheitsfaktor. Ein Spike hat ihn erledigt: `VNGenerateFaceprintRequest` gibt es nicht, aber `VNGenerateImageFeaturePrintRequest` ist öffentlich, liefert 768 Elemente und ein Abstandsmaß — Faktor 3,5 Trennschärfe. Kein eigenes CoreML-Modell nötig. `FaceMatcher` entscheidet über mehrere Posen, mehrere Einzelbilder und eine Sperre nach Fehlversuchen. Die Schwelle liegt bewusst nicht mittig: ein falsches Ja ist ein Fremder am entsperrten Rechner, ein falsches Nein nur ein Passwort, das man ohnehin kennt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
f289d02c37
commit
3da673662f
@@ -0,0 +1,71 @@
|
||||
import Foundation
|
||||
|
||||
/// Eine kurze Notiz.
|
||||
///
|
||||
/// Kein Titel als eigenes Feld: wer eine Notiz schreibt, will schreiben und
|
||||
/// nicht erst benennen. Die Überschrift ist die erste Zeile — dieselbe
|
||||
/// Verabredung, die Apples Notizen seit jeher treffen.
|
||||
public struct Note: Identifiable, Equatable, Sendable, Codable {
|
||||
public let id: UUID
|
||||
public var text: String
|
||||
public var updatedAt: Date
|
||||
|
||||
public init(id: UUID = UUID(), text: String = "", updatedAt: Date = Date()) {
|
||||
self.id = id
|
||||
self.text = text
|
||||
self.updatedAt = updatedAt
|
||||
}
|
||||
}
|
||||
|
||||
/// Die Regeln der Notizen — ohne Dateisystem und ohne Uhr.
|
||||
public enum NoteRules {
|
||||
|
||||
/// Wie viele Notizen höchstens.
|
||||
///
|
||||
/// Die Kachel ist klein und die Liste soll überschaubar bleiben. Wer mehr
|
||||
/// braucht, braucht ein Notizprogramm und keine Notch.
|
||||
public static let limit = 50
|
||||
|
||||
/// Wie lang eine Überschrift höchstens ist.
|
||||
public static let titleLength = 60
|
||||
|
||||
/// Die erste nichtleere Zeile, gekürzt. Leer bleibt leer — daraus macht die
|
||||
/// Anzeige „Neue Notiz", und das ist ihre Sache, nicht unsere.
|
||||
public static func title(of note: Note) -> String {
|
||||
let line = note.text
|
||||
.split(whereSeparator: \.isNewline)
|
||||
.first { !$0.trimmingCharacters(in: .whitespaces).isEmpty }
|
||||
guard let line else { return "" }
|
||||
let trimmed = line.trimmingCharacters(in: .whitespaces)
|
||||
guard trimmed.count > titleLength else { return trimmed }
|
||||
return String(trimmed.prefix(titleLength)) + "…"
|
||||
}
|
||||
|
||||
/// Ob eine Notiz überhaupt etwas enthält.
|
||||
///
|
||||
/// Eine Notiz aus lauter Leerzeichen ist keine — sie entsteht, wenn man auf
|
||||
/// „Neu" drückt und es sich anders überlegt, und soll dann von selbst
|
||||
/// verschwinden statt als leere Zeile stehen zu bleiben.
|
||||
public static func isEmpty(_ note: Note) -> Bool {
|
||||
note.text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
|
||||
}
|
||||
|
||||
/// Zuletzt geändert zuerst, mit fester Reihenfolge bei Gleichstand.
|
||||
public static func sorted(_ notes: [Note]) -> [Note] {
|
||||
notes.sorted {
|
||||
$0.updatedAt == $1.updatedAt
|
||||
? $0.id.uuidString > $1.id.uuidString
|
||||
: $0.updatedAt > $1.updatedAt
|
||||
}
|
||||
}
|
||||
|
||||
/// Nimmt eine geänderte Notiz auf: leere fallen weg, über der Grenze fällt
|
||||
/// die älteste heraus.
|
||||
public static func merge(_ notes: [Note], updating note: Note,
|
||||
limit: Int = limit) -> [Note] {
|
||||
var kept = notes.filter { $0.id != note.id }
|
||||
if !isEmpty(note) { kept.append(note) }
|
||||
let sorted = sorted(kept)
|
||||
return Array(sorted.prefix(limit))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user