Add findmy ring, findmy phone and device aliases - #26
Conversation
Ported from feat/locale-support, a May branch that never landed. Main has since re-implemented that branch's locale, zoom and Space handling independently, so merging it conflicts in five files including an add/add on locale.go. These two features are the part that was never rebuilt, so they are ported fresh against today's code rather than merged. `ring` scrolls the Devices sidebar until it OCRs the target row, clicks it, double-clicks the map to raise the pin's detail card, and clicks "Play Sound". None of those steps report completion, so each one retries: the double-click runs up to five times with growing delays, and the button is polled for rather than waited on. That retry ladder is what the original branch's reliability work produced, and it is kept verbatim. It is a **dry run unless you pass --confirm**. The original had `phone` ring immediately while `ring` was gated, which meant the safety depended on which name you typed; here both go through the same gate and `phone` is simply the shorthand that defaults to the "phone" alias. Aliases live in ~/.config/findmy-cli/aliases.json and resolve case-insensitively, so `findmy ring phone --confirm` works once set. Verified: builds, `go vet` clean, the Swift helper compiles and reports its new `scroll` subcommand, and the alias surface is exercised end to end against a temp HOME (set, list, case-insensitive overwrite, delete, and the two usage errors). Nine new tests, including the localized button labels and a corrupt aliases.json, which must never take the CLI down since every command resolves aliases. The ring path itself drives the Find My UI and is NOT verified end-to-end -- run `findmy ring <device>` without --confirm to dry-run it. Not ported from that branch: the BetterDisplay virtual-display mode and six benchmark/optimization shell scripts. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SdvrUy3dukjMh6GNaUC9iq
|
| Filename | Overview |
|---|---|
| internal/findmy/ring.go | Implements the ring workflow, but stale-card reuse, first-substring matching, and remove-before-scale behavior can target the wrong device or fail lookup. |
| cmd/findmy/main.go | Adds ring, phone, and alias commands; prior-application capture occurs too late and lookup errors bypass restoration. |
| internal/findmy/aliases.go | Adds tolerant JSON-backed, case-insensitive alias persistence and resolution. |
| helpers/findmy-helper/main.swift | Adds continuous phased scroll-event synthesis for the Find My sidebar. |
| internal/findmy/ring_test.go | Covers label matching and alias persistence but does not exercise ambiguous device selection, stale cards, or command-level alias support. |
| README.md | Documents ringing and aliases, including a broader alias-support claim than the implementation provides. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[findmy ring device or alias] --> B[Resolve alias]
B --> C[Prepare Find My Devices tab]
C --> D[Scroll and OCR sidebar]
D --> E[Return matching DeviceHit]
E --> F{Play Sound already visible?}
F -- Yes --> G[Click or report existing button]
F -- No --> H[Click matched sidebar row]
H --> I[Double-click map and poll OCR]
I --> J[Click or report Play Sound]
G --> K[Switch tab and restore prior app]
J --> K
Prompt To Fix All With AI
### Issue 1
internal/findmy/ring.go:211-216
**Stale Card Rings Wrong Device**
When another device’s detail card is already open, this fast path clicks its visible Play Sound button before selecting or verifying the requested device. A confirmed request for device B can therefore ring device A. Select the requested row before using the existing card, or verify that the card belongs to the requested device.
### Issue 2
internal/findmy/ring.go:171-174
**Partial Match Selects Wrong Device**
This returns the first substring match, so similarly named devices such as “Omar’s iPhone” and “Omar’s iPhone 15” can select and ring the wrong row depending on sidebar order. Prefer an exact normalized match across the visible frame before falling back to an explicitly handled partial match.
### Issue 3
internal/findmy/ring.go:163-166
**Screenshot Removed Before Scale Read**
The screenshot is removed before `imageScaleFor` reads it, so this calculation always uses the 2.0 fallback. On supported 1x or non-integer-scale displays, the resulting sidebar boundary is wrong and can admit detail-pane text as a device match or exclude valid sidebar rows, causing lookup failure or selection of the wrong coordinates.
### Issue 4
cmd/findmy/main.go:935-945
**Previous App Is Not Restored**
The prior application is remembered inside `RingDevice`, after `PrepareDevices` has already activated and foregrounded Find My, so successful runs record `com.apple.findmy` and restoration becomes a no-op. Device-search errors also exit before the inline cleanup. Capture the prior app before preparation and arrange cleanup for all post-activation returns to satisfy the documented promise to switch back.
### Issue 5
README.md:126
**Alias Support Is Overstated**
The documentation says any command that takes a device accepts aliases, but alias resolution is wired only into `ring` and `phone`. For example, `findmy device phone` still searches for the literal name `phone`, so users following this documentation get no matching device. Either resolve aliases in the existing device command paths or narrow this claim.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Reviews (1): Last reviewed commit: "Add `findmy ring`, `findmy phone` and de..." | Re-trigger Greptile
| // Fast path: the card may already be open from a previous run. | ||
| if lines, err := captureAndOCR(w, tmpDir); err == nil { | ||
| if btn := findPlaySoundButton(lines); btn != nil { | ||
| return clickOrDryRun(w, btn, scale, dryRun) | ||
| } | ||
| } |
There was a problem hiding this comment.
When another device’s detail card is already open, this fast path clicks its visible Play Sound button before selecting or verifying the requested device. A confirmed request for device B can therefore ring device A. Select the requested row before using the existing card, or verify that the card belongs to the requested device.
Prompt To Fix With AI
This is a comment left during a code review.
Path: internal/findmy/ring.go
Line: 211-216
Comment:
**Stale Card Rings Wrong Device**
When another device’s detail card is already open, this fast path clicks its visible Play Sound button before selecting or verifying the requested device. A confirmed request for device B can therefore ring device A. Select the requested row before using the existing card, or verify that the card belongs to the requested device.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.| txt := strings.TrimSpace(l.Text) | ||
| if strings.Contains(strings.ToLower(txt), targetLower) { | ||
| return &DeviceHit{Name: txt, NameX: l.X, NameY: l.Y}, nil | ||
| } |
There was a problem hiding this comment.
Partial Match Selects Wrong Device
This returns the first substring match, so similarly named devices such as “Omar’s iPhone” and “Omar’s iPhone 15” can select and ring the wrong row depending on sidebar order. Prefer an exact normalized match across the visible frame before falling back to an explicitly handled partial match.
Prompt To Fix With AI
This is a comment left during a code review.
Path: internal/findmy/ring.go
Line: 171-174
Comment:
**Partial Match Selects Wrong Device**
This returns the first substring match, so similarly named devices such as “Omar’s iPhone” and “Omar’s iPhone 15” can select and ring the wrong row depending on sidebar order. Prefer an exact normalized match across the visible frame before falling back to an explicitly handled partial match.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.| return nil, fmt.Errorf("ocr: %w", err) | ||
| } | ||
|
|
||
| sidebarRightPx := int(340 * imageScaleFor(w, shot)) |
There was a problem hiding this comment.
Screenshot Removed Before Scale Read
The screenshot is removed before imageScaleFor reads it, so this calculation always uses the 2.0 fallback. On supported 1x or non-integer-scale displays, the resulting sidebar boundary is wrong and can admit detail-pane text as a device match or exclude valid sidebar rows, causing lookup failure or selection of the wrong coordinates.
Prompt To Fix With AI
This is a comment left during a code review.
Path: internal/findmy/ring.go
Line: 163-166
Comment:
**Screenshot Removed Before Scale Read**
The screenshot is removed before `imageScaleFor` reads it, so this calculation always uses the 2.0 fallback. On supported 1x or non-integer-scale displays, the resulting sidebar boundary is wrong and can admit detail-pane text as a device match or exclude valid sidebar rows, causing lookup failure or selection of the wrong coordinates.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.| w, err := findmy.PrepareDevices() | ||
| must(err) | ||
|
|
||
| match, err := findmy.FindDeviceByScroll(w, resolved, tmpDir()) | ||
| must(err) | ||
| fmt.Fprintf(os.Stderr, "Found: %s\n", match.Name) | ||
|
|
||
| err = findmy.RingDevice(w, match, tmpDir(), !opts.confirm) | ||
| _ = findmy.SwitchTab(findmy.GetAppStrings().PeopleTab) | ||
| findmy.RestoreUserSpace() | ||
| must(err) |
There was a problem hiding this comment.
The prior application is remembered inside RingDevice, after PrepareDevices has already activated and foregrounded Find My, so successful runs record com.apple.findmy and restoration becomes a no-op. Device-search errors also exit before the inline cleanup. Capture the prior app before preparation and arrange cleanup for all post-activation returns to satisfy the documented promise to switch back.
Prompt To Fix With AI
This is a comment left during a code review.
Path: cmd/findmy/main.go
Line: 935-945
Comment:
**Previous App Is Not Restored**
The prior application is remembered inside `RingDevice`, after `PrepareDevices` has already activated and foregrounded Find My, so successful runs record `com.apple.findmy` and restoration becomes a no-op. Device-search errors also exit before the inline cleanup. Capture the prior app before preparation and arrange cleanup for all post-activation returns to satisfy the documented promise to switch back.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.| findmy alias phone "Omar's iPhone" # set | ||
| findmy alias # list | ||
| findmy alias --delete phone # remove | ||
| findmy ring phone --confirm # any command that takes a device accepts one |
There was a problem hiding this comment.
The documentation says any command that takes a device accepts aliases, but alias resolution is wired only into ring and phone. For example, findmy device phone still searches for the literal name phone, so users following this documentation get no matching device. Either resolve aliases in the existing device command paths or narrow this claim.
Prompt To Fix With AI
This is a comment left during a code review.
Path: README.md
Line: 126
Comment:
**Alias Support Is Overstated**
The documentation says any command that takes a device accepts aliases, but alias resolution is wired only into `ring` and `phone`. For example, `findmy device phone` still searches for the literal name `phone`, so users following this documentation get no matching device. Either resolve aliases in the existing device command paths or narrow this claim.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.All four Greptile raised are real; three would ring or select the wrong thing. 1. The scale was read from a screenshot I had already deleted, so `imageScaleFor` always fell back to 2.0 and the sidebar boundary was wrong on any display that is not 2x. Read the scale before OCR removes the file, and only once per search. This one was mine, not the original branch's. 2. Device matching returned the first substring hit, so "Omar's iPhone" could select "Omar's iPhone 15" purely on sidebar order. An exact name now wins over a substring anywhere in the frame, via `matchSidebarDevice`. 3. The fast path clicked whatever "Play Sound" button was already on screen. A card left open for another device shows its own button, so a confirmed ring for device B could ring device A. `cardShowsDevice` now requires the open card -- text to the RIGHT of the sidebar, since the sidebar always contains the name once we have scrolled to it -- to name the requested device. 4. The frontmost app was recorded inside RingDevice, after PrepareDevices had already activated Find My, so it recorded Find My and the restore was a no-op. It is captured before preparation now. The restore also sat on the happy path only; it is a defer, and `ring` returns its errors instead of calling `must`, because os.Exit does not run deferred functions. Seven more tests. Verified they bite: reverting the match to substring-first and stubbing the card check to true fails exactly the three tests that cover those behaviours. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SdvrUy3dukjMh6GNaUC9iq
Resurrects the two features from
feat/locale-support(May 15) that never reached main. Main has since re-implemented that branch's locale, zoom and Space handling independently, so a merge conflicts in five files including an add/add onlocale.go. These are ported fresh against today's code instead.findmy ring <device|alias> [--confirm]Scrolls the Devices sidebar until it OCRs the target row, clicks it, double-clicks the map to raise the pin's detail card, and clicks Play Sound. Nothing in that sequence reports completion, so every step retries: the double-click runs up to five times with growing delays, and the button is polled for rather than slept on. That ladder is the original branch's reliability work, kept as-is.
Dry run unless you pass
--confirm. The original hadphonering immediately whileringwas gated, so the safety depended on which name you typed. Here both go through the same gate, andphoneis just the shorthand that defaults to thephonealias.Aliases
~/.config/findmy-cli/aliases.json, resolved case-insensitively:Verification
Builds,
go vetclean, 9 new tests pass (localized button labels, scale fallback, alias round-trip, and a corruptaliases.json— which must never take the CLI down, since every command resolves aliases). The Swift helper compiles and advertises its newscrollsubcommand. The alias surface is exercised end to end against a temp HOME: set, list, case-insensitive overwrite, delete, and both usage errors.The ring path is not verified end-to-end. It drives the Find My UI, and verifying it means audibly ringing a device. Dry-run it with
findmy ring "<device>"(no--confirm) — it will locate the button and print the coordinates it would have clicked.Not ported
The branch's BetterDisplay virtual-display mode (depends on a third-party app) and six benchmark/optimization shell scripts.
🤖 Generated with Claude Code
https://claude.ai/code/session_01SdvrUy3dukjMh6GNaUC9iq