From 890b500647e172f7d0b1aec6b75aae60b15dc94b Mon Sep 17 00:00:00 2001 From: Akshay Kolli Date: Tue, 30 Jun 2026 10:44:11 -0700 Subject: [PATCH] WIP: avoid duplicated text card body --- docs/SMOKE_TEST.md | 1 + .../clipbored/views/ClipboardPanelView.swift | 50 +++++++++++++------ .../ClipboardPanelViewTests.swift | 20 ++++++++ 3 files changed, 57 insertions(+), 14 deletions(-) diff --git a/docs/SMOKE_TEST.md b/docs/SMOKE_TEST.md index e1515b3..0d875b2 100644 --- a/docs/SMOKE_TEST.md +++ b/docs/SMOKE_TEST.md @@ -59,6 +59,7 @@ Use this checklist before a release or after changes to panel, pasteboard, setti 26. 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. 27. Confirm card headers use readable relative ages such as `3 minutes ago` or `2 hours ago`, including when viewing a named collection. 28. Confirm the selected card shows a green corner Stack control, the action rail does not duplicate Stack, and clips added to Stack keep a visible corner indicator when selection moves away. +29. Confirm single-line text cards do not repeat the same text in both title and body, while multi-line text cards show the remaining lines below the first line. ## Copy And Paste diff --git a/sources/clipbored/views/ClipboardPanelView.swift b/sources/clipbored/views/ClipboardPanelView.swift index 0048c01..b49d564 100644 --- a/sources/clipbored/views/ClipboardPanelView.swift +++ b/sources/clipbored/views/ClipboardPanelView.swift @@ -1338,6 +1338,14 @@ final class ClipboardPanelView: NSVisualEffectView, NSSearchFieldDelegate { cardViews.map(\.debugPreviewSummary) } + var debugCardTextPreviewTitles: [String] { + cardViews.map(\.debugTextPreviewTitle) + } + + var debugCardTextPreviewBodies: [String] { + cardViews.map(\.debugTextPreviewBody) + } + var debugCardPreviewStyles: [String] { cardViews.map(\.debugPreviewStyle) } @@ -2680,6 +2688,8 @@ private final class ClipboardItemCardView: NSView, NSDraggingSource { private(set) var debugHeaderTitle = "" private(set) var debugHeaderSubtitle = "" private(set) var debugHeaderColorHex = "" + private(set) var debugTextPreviewTitle = "" + private(set) var debugTextPreviewBody = "" var debugMenuTitles: [String] { contextMenu().items.map { $0.isSeparatorItem ? "-" : $0.title } @@ -3517,28 +3527,39 @@ private final class ClipboardItemCardView: NSView, NSDraggingSource { let titleString = titleText(for: item) let bodyString = previewBodyText(for: item, title: titleString) + #if DEBUG + debugTextPreviewTitle = titleString + debugTextPreviewBody = bodyString ?? "" + #endif let title = NSTextField(wrappingLabelWithString: titleString) - title.font = .systemFont(ofSize: 13, weight: .semibold) + title.font = bodyString == nil + ? .systemFont(ofSize: item.kind == .richText ? 15 : 14, weight: .regular) + : .systemFont(ofSize: 13, weight: .semibold) title.textColor = .labelColor - title.maximumNumberOfLines = 1 + title.maximumNumberOfLines = bodyString == nil ? 5 : 1 title.lineBreakMode = .byTruncatingTail title.toolTip = title.stringValue - let detail = NSTextField(wrappingLabelWithString: bodyString) - detail.font = .systemFont(ofSize: item.kind == .richText ? 15 : 14) - detail.textColor = .secondaryLabelColor - detail.maximumNumberOfLines = 5 - detail.lineBreakMode = .byTruncatingTail - detail.toolTip = detail.stringValue + var textViews: [NSView] = [title] + if let bodyString { + let detail = NSTextField(wrappingLabelWithString: bodyString) + detail.font = .systemFont(ofSize: item.kind == .richText ? 15 : 14) + detail.textColor = .secondaryLabelColor + detail.maximumNumberOfLines = 5 + detail.lineBreakMode = .byTruncatingTail + detail.toolTip = detail.stringValue + textViews.append(detail) + } - let stack = NSStackView(views: [title, detail]) + let stack = NSStackView(views: textViews) stack.orientation = .vertical stack.alignment = .leading - stack.spacing = 10 + stack.spacing = bodyString == nil ? 0 : 10 stack.translatesAutoresizingMaskIntoConstraints = false container.addSubview(stack) - title.widthAnchor.constraint(equalTo: stack.widthAnchor).isActive = true - detail.widthAnchor.constraint(equalTo: stack.widthAnchor).isActive = true + for view in textViews { + view.widthAnchor.constraint(equalTo: stack.widthAnchor).isActive = true + } NSLayoutConstraint.activate([ stack.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: layout.inset), stack.trailingAnchor.constraint(equalTo: container.trailingAnchor, constant: -layout.inset), @@ -3846,11 +3867,11 @@ private final class ClipboardItemCardView: NSView, NSDraggingSource { return label } - private func previewBodyText(for item: ClipboardItem, title: String) -> String { + private func previewBodyText(for item: ClipboardItem, title: String) -> String? { let preview = previewText(for: item) let normalizedTitle = normalized(title) if preview == normalizedTitle { - return preview + return nil } let prefix = normalizedTitle + " " @@ -3859,6 +3880,7 @@ private final class ClipboardItemCardView: NSView, NSDraggingSource { if !remainder.isEmpty { return remainder } + return nil } return preview diff --git a/tests/clipboredtests/ClipboardPanelViewTests.swift b/tests/clipboredtests/ClipboardPanelViewTests.swift index a19a720..f7d7fe0 100644 --- a/tests/clipboredtests/ClipboardPanelViewTests.swift +++ b/tests/clipboredtests/ClipboardPanelViewTests.swift @@ -65,6 +65,26 @@ final class ClipboardPanelViewTests: XCTestCase { XCTAssertEqual(fixture.view.debugCardRailOverflowFadeVisibility, [false, false]) } + func testSingleLineTextCardsDoNotDuplicateTitleInBody() { + let fixture = makePanelFixture() + fixture.store.upsert(makeTextItem("Client follow-up note", store: fixture.store)) + drainMainQueue() + fixture.window.contentView?.layoutSubtreeIfNeeded() + + XCTAssertEqual(fixture.view.debugCardTextPreviewTitles, ["Client follow-up note"]) + XCTAssertEqual(fixture.view.debugCardTextPreviewBodies, [""]) + } + + func testMultiLineTextCardsShowRemainderBelowTitle() { + let fixture = makePanelFixture() + fixture.store.upsert(makeTextItem("Address:\n399 The Embarcadero\nSan Francisco", store: fixture.store)) + drainMainQueue() + fixture.window.contentView?.layoutSubtreeIfNeeded() + + XCTAssertEqual(fixture.view.debugCardTextPreviewTitles, ["Address:"]) + XCTAssertEqual(fixture.view.debugCardTextPreviewBodies, ["399 The Embarcadero San Francisco"]) + } + func testCompactCardsFitTwoItemsOnNarrowDockShelf() { let fixture = makePanelFixture() fixture.window.setFrame(NSRect(x: 0, y: 0, width: 620, height: 520), display: true)