WIP: quiet card footer metadata

This commit is contained in:
Akshay Kolli
2026-06-30 10:14:25 -07:00
parent ac285aed89
commit 241596acb3
3 changed files with 71 additions and 11 deletions

View File

@@ -55,6 +55,7 @@ Use this checklist before a release or after changes to panel, pasteboard, setti
22. Drag an unassigned card onto the renamed collection chip and confirm the chip count increases and the card appears when that collection is selected. 22. Drag an unassigned card onto the renamed collection chip and confirm the chip count increases and the card appears when that collection is selected.
23. Resize or test on a narrow display and confirm the bottom shelf switches to compact cards that still show two recent clips cleanly. 23. Resize or test on a narrow display and confirm the bottom shelf switches to compact cards that still show two recent clips cleanly.
24. Select a file, rich text, or URL card and confirm the selected-card rail exposes `Paste Plain Text`, the corner source/kind badge remains visible, and on a narrow shelf secondary actions collapse behind `More` instead of overflowing the card. 24. Select a file, rich text, or URL card and confirm the selected-card rail exposes `Paste Plain Text`, the corner source/kind badge remains visible, and on a narrow shelf secondary actions collapse behind `More` instead of overflowing the card.
25. Confirm card footers do not show `Unknown` for clips without a source app, and confirm used clips show their usage count beside the source app.
## Copy And Paste ## Copy And Paste

View File

@@ -1370,6 +1370,14 @@ final class ClipboardPanelView: NSVisualEffectView, NSSearchFieldDelegate {
cardViews.first?.debugFooterDetailText ?? "" cardViews.first?.debugFooterDetailText ?? ""
} }
var debugFirstCardFooterSourceText: String {
cardViews.first?.debugFooterSourceText ?? ""
}
var debugFirstCardFooterSourceIsHidden: Bool {
cardViews.first?.debugFooterSourceIsHidden ?? true
}
var debugQuickPasteBadgeTexts: [String] { var debugQuickPasteBadgeTexts: [String] {
cardViews.compactMap(\.debugQuickPasteBadgeText) cardViews.compactMap(\.debugQuickPasteBadgeText)
} }
@@ -2401,6 +2409,7 @@ private final class ClipboardItemCardView: NSView, NSDraggingSource {
private let activeCollectionColor: NSColor? private let activeCollectionColor: NSColor?
private let collectionNames: [String] private let collectionNames: [String]
private let contentView = NSView() private let contentView = NSView()
private let footerSourceLabel = NSTextField(labelWithString: "")
private let footerDetailLabel = NSTextField(labelWithString: "") private let footerDetailLabel = NSTextField(labelWithString: "")
private let actionRail = NSStackView() private let actionRail = NSStackView()
private var actionRailButtons: [NSButton] = [] private var actionRailButtons: [NSButton] = []
@@ -2697,6 +2706,14 @@ private final class ClipboardItemCardView: NSView, NSDraggingSource {
footerDetailLabel.stringValue footerDetailLabel.stringValue
} }
var debugFooterSourceText: String {
footerSourceLabel.stringValue
}
var debugFooterSourceIsHidden: Bool {
footerSourceLabel.isHidden
}
var debugItemID: UUID { var debugItemID: UUID {
itemID itemID
} }
@@ -3786,12 +3803,14 @@ private final class ClipboardItemCardView: NSView, NSDraggingSource {
footer.layer?.backgroundColor = Palette.footerBackground footer.layer?.backgroundColor = Palette.footerBackground
footer.heightAnchor.constraint(equalToConstant: layout.footerHeight).isActive = true footer.heightAnchor.constraint(equalToConstant: layout.footerHeight).isActive = true
let source = NSTextField(labelWithString: sourceText(for: item)) let sourceText = footerSourceText(for: item)
source.font = .systemFont(ofSize: NSFont.smallSystemFontSize, weight: .medium) footerSourceLabel.stringValue = sourceText ?? ""
source.textColor = .secondaryLabelColor footerSourceLabel.font = .systemFont(ofSize: NSFont.smallSystemFontSize, weight: .medium)
source.lineBreakMode = .byTruncatingTail footerSourceLabel.textColor = .secondaryLabelColor
source.maximumNumberOfLines = 1 footerSourceLabel.lineBreakMode = .byTruncatingTail
source.toolTip = source.stringValue footerSourceLabel.maximumNumberOfLines = 1
footerSourceLabel.toolTip = footerSourceLabel.stringValue
footerSourceLabel.isHidden = sourceText == nil
let detailText = detailMetricText(for: item) let detailText = detailMetricText(for: item)
if activeCollectionName == nil, if activeCollectionName == nil,
@@ -3814,12 +3833,12 @@ private final class ClipboardItemCardView: NSView, NSDraggingSource {
divider.wantsLayer = true divider.wantsLayer = true
divider.layer?.backgroundColor = Palette.divider divider.layer?.backgroundColor = Palette.divider
divider.translatesAutoresizingMaskIntoConstraints = false divider.translatesAutoresizingMaskIntoConstraints = false
let stack = row([source, footerDetailLabel]) let stack = row([footerSourceLabel, footerDetailLabel])
stack.distribution = .fill stack.distribution = .fill
stack.alignment = .centerY stack.alignment = .centerY
stack.translatesAutoresizingMaskIntoConstraints = false stack.translatesAutoresizingMaskIntoConstraints = false
source.setContentHuggingPriority(.defaultLow, for: .horizontal) footerSourceLabel.setContentHuggingPriority(.defaultLow, for: .horizontal)
source.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) footerSourceLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
footerDetailLabel.setContentHuggingPriority(.required, for: .horizontal) footerDetailLabel.setContentHuggingPriority(.required, for: .horizontal)
footerDetailLabel.setContentCompressionResistancePriority(.required, for: .horizontal) footerDetailLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
footer.addSubview(divider) footer.addSubview(divider)
@@ -3989,8 +4008,21 @@ private final class ClipboardItemCardView: NSView, NSDraggingSource {
} }
} }
private func sourceText(for item: ClipboardItem) -> String { private func footerSourceText(for item: ClipboardItem) -> String? {
item.sourceApp?.clipboardTrimmed.isEmpty == false ? item.sourceApp! : "Unknown" let source = item.sourceApp?.clipboardTrimmed
let usage = usageText(for: item.useCount)
if let source, !source.isEmpty, let usage {
return "\(source) - \(usage)"
}
if let source, !source.isEmpty {
return source
}
return usage
}
private func usageText(for useCount: Int) -> String? {
guard useCount > 0 else { return nil }
return useCount == 1 ? "Used once" : "Used \(useCount) times"
} }
private func linkTitle(for item: ClipboardItem) -> String { private func linkTitle(for item: ClipboardItem) -> String {

View File

@@ -106,6 +106,33 @@ final class ClipboardPanelViewTests: XCTestCase {
XCTAssertFalse(fixture.view.debugStatusText.contains("Enter paste")) XCTAssertFalse(fixture.view.debugStatusText.contains("Enter paste"))
} }
func testCardFooterHidesMissingSourceInsteadOfShowingUnknown() {
let fixture = makePanelFixture()
var item = makeTextItem("No source noise", store: fixture.store)
item.sourceApp = nil
fixture.store.upsert(item)
drainMainQueue()
fixture.window.contentView?.layoutSubtreeIfNeeded()
XCTAssertTrue(fixture.view.debugFirstCardFooterSourceIsHidden)
XCTAssertEqual(fixture.view.debugFirstCardFooterSourceText, "")
XCTAssertEqual(fixture.view.debugFirstCardFooterDetailText, "15 characters")
}
func testCardFooterShowsSourceAndUsageWhenUsed() {
let fixture = makePanelFixture()
var item = makeTextItem("Frequently pasted", store: fixture.store)
item.useCount = 3
fixture.store.upsert(item)
drainMainQueue()
fixture.window.contentView?.layoutSubtreeIfNeeded()
XCTAssertFalse(fixture.view.debugFirstCardFooterSourceIsHidden)
XCTAssertEqual(fixture.view.debugFirstCardFooterSourceText, "Ghostty - Used 3 times")
}
func testEditedTextStatusUsesActionTone() { func testEditedTextStatusUsesActionTone() {
let fixture = makePanelFixture() let fixture = makePanelFixture()
fixture.store.upsert(makeTextItem("Editable footer item", store: fixture.store)) fixture.store.upsert(makeTextItem("Editable footer item", store: fixture.store))