Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .jules/palette.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## 2024-05-24 - Safely Display Tooltips on Disabled Buttons
**Learning:** Native HTML `title` tooltips and `cursor-not-allowed` styles are suppressed on `button` elements that have the native `disabled` attribute because disabled elements do not emit pointer events in most browsers.
**Action:** When adding tooltips to disabled elements, wrap the element in a layout-preserving container (like `<span className="flex">` or `inline-flex`), apply the `title` and `cursor-not-allowed` logic to the wrapper, and use `disabled:pointer-events-none` on the inner button itself so pointer events pass through to the wrapper.
39 changes: 22 additions & 17 deletions src/features/library/components/MatchPreviewModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -246,24 +246,29 @@ export function MatchPreviewModal({ preview, onConfirm, onClose, onConfirmed }:
Cancel
</button>
{!noCandidates && (
<button
type="button"
onClick={handleApprove}
disabled={isApplying || !selected}
className="flex items-center gap-2 rounded-lg bg-accent-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-accent-700 disabled:opacity-50"
<span
className={cx('flex', (isApplying || !selected) && 'cursor-not-allowed')}
title={!selected ? 'Select a candidate to approve' : isApplying ? 'Applying match...' : 'Approve Match'}
>
{isApplying ? (
<>
<Loader2 size={14} className="animate-spin" />
Applying...
</>
) : (
<>
<Check size={14} />
Approve Match
</>
)}
</button>
<button
type="button"
onClick={handleApprove}
disabled={isApplying || !selected}
className="flex items-center gap-2 rounded-lg bg-accent-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-accent-700 disabled:pointer-events-none disabled:opacity-50"
>
{isApplying ? (
<>
<Loader2 size={14} className="animate-spin" />
Applying...
</>
) : (
<>
<Check size={14} />
Approve Match
</>
)}
</button>
</span>
)}
</div>
</div>
Expand Down
19 changes: 12 additions & 7 deletions src/features/library/components/UnresolvedFilesSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -165,14 +165,19 @@ export function UnresolvedFilesSection({
</button>
) : null}
</div>
<button
className="flex items-center gap-2 rounded-lg bg-accent-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-accent-700 disabled:opacity-50"
disabled={isAttemptMatchPending || isAttemptMatchAllPending || discoveredItems.length === 0}
onClick={onAttemptMatchAll}
<span
className={cx('flex', (isAttemptMatchPending || isAttemptMatchAllPending || discoveredItems.length === 0) && 'cursor-not-allowed')}
title={discoveredItems.length === 0 ? 'No unresolved files to match' : isAttemptMatchPending || isAttemptMatchAllPending ? 'Matching in progress...' : 'Match all unresolved files'}
>
<RefreshCw size={14} className={cx(isAttemptMatchAllPending && 'animate-spin')} />
{isAttemptMatchAllPending ? 'Matching All...' : 'Match All'}
</button>
<button
className="flex items-center gap-2 rounded-lg bg-accent-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-accent-700 disabled:pointer-events-none disabled:opacity-50"
disabled={isAttemptMatchPending || isAttemptMatchAllPending || discoveredItems.length === 0}
onClick={onAttemptMatchAll}
>
<RefreshCw size={14} className={cx(isAttemptMatchAllPending && 'animate-spin')} />
{isAttemptMatchAllPending ? 'Matching All...' : 'Match All'}
</button>
</span>
</div>
</div>

Expand Down
19 changes: 19 additions & 0 deletions verification/test.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link href="http://localhost:1420/src/index.css" rel="stylesheet">
</head>
<body class="p-8 bg-slate-50 flex gap-4">
<span class="flex cursor-not-allowed" title="No unresolved files to match">
<button class="flex items-center gap-2 rounded-lg bg-accent-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-accent-700 disabled:pointer-events-none disabled:opacity-50" disabled>
Match All
</button>
</span>
<span class="flex cursor-not-allowed" title="Select a candidate to approve">
<button class="flex items-center gap-2 rounded-lg bg-accent-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-accent-700 disabled:pointer-events-none disabled:opacity-50" disabled>
Approve Match
</button>
</span>
</body>
</html>
20 changes: 20 additions & 0 deletions verification/verify.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page()
page.goto("file:///app/verification/test.html")
page.wait_for_timeout(1000)
page.screenshot(path="/home/jules/verification/screenshot.png")

# Hover over Match All button wrapper
page.locator("span[title='No unresolved files to match']").hover(force=True)
page.wait_for_timeout(500)
page.screenshot(path="/home/jules/verification/match_all_hover.png")

# Hover over Approve Match button wrapper
page.locator("span[title='Select a candidate to approve']").hover(force=True)
page.wait_for_timeout(500)
page.screenshot(path="/home/jules/verification/approve_match_hover.png")

browser.close()