Fix Commander emoji paste recovery and picker overflow - #662
Conversation
✅ Develop S3 preview ready
The alias passed the develop bucket CORS preflight and a final live PR/SHA fence. Generic Vercel Preview deployments use the shared development runtime; this controller adds the stable exact-SHA alias and marker-scoped cleanup. |
|
Lopu review — PR #662 (head Reviewed and looks good. No changes made; none were justified. The
|
| Command | Result |
|---|---|
pnpm build:protocol |
exit 0 |
tsc --noEmit (desktop-ui) |
exit 0 |
vitest run EmojiPicker.test.tsx |
8/8 passed |
vitest run (full desktop-ui) |
13 files, 79/79 passed |
The XCTest target could not run here — Linux runner, no macOS/AppKit — so testDeniedEmojiPasteDoesNotTouchTheClipboard was reviewed by reading. Its assertions match the implementation.
Posted by Lopu, Thingtime's automated reviewer.
Lopu repository reviewLopu reviewed this PR against main as Thingtime's principal PR and repository manager. Using Claude Opus 5. Lopu found no justified local change to publish from this review pass. The model did not leave a detailed report; inspect this workflow run for the Lopu session log. |
…erflow # Conflicts: # graphify-out/snapshots/v1/379189ef7046444518f6310ae62a404aa898fec3734db662fa60500f0966f65b/3446d7e121b850cb95583c7389f3de3fd6c4af453a50e0f175fbddc78d750595/cost.json # graphify-out/snapshots/v1/379189ef7046444518f6310ae62a404aa898fec3734db662fa60500f0966f65b/3446d7e121b850cb95583c7389f3de3fd6c4af453a50e0f175fbddc78d750595/graph.json # graphify-out/snapshots/v1/379189ef7046444518f6310ae62a404aa898fec3734db662fa60500f0966f65b/3446d7e121b850cb95583c7389f3de3fd6c4af453a50e0f175fbddc78d750595/manifest.json # graphify-out/snapshots/v1/904fc6c923923593a0ea65afcb9a357a9c7d4e1f2ee8da0d2d7b2c8cab5ab3fb/934fb6de6ec193ed6fc549e581df7491ea5ac02df80feda512b7f6df509d28e0/GRAPH_REPORT.md # graphify-out/snapshots/v1/904fc6c923923593a0ea65afcb9a357a9c7d4e1f2ee8da0d2d7b2c8cab5ab3fb/934fb6de6ec193ed6fc549e581df7491ea5ac02df80feda512b7f6df509d28e0/cost.json # graphify-out/snapshots/v1/904fc6c923923593a0ea65afcb9a357a9c7d4e1f2ee8da0d2d7b2c8cab5ab3fb/934fb6de6ec193ed6fc549e581df7491ea5ac02df80feda512b7f6df509d28e0/graph.json # graphify-out/snapshots/v1/904fc6c923923593a0ea65afcb9a357a9c7d4e1f2ee8da0d2d7b2c8cab5ab3fb/934fb6de6ec193ed6fc549e581df7491ea5ac02df80feda512b7f6df509d28e0/manifest.json # graphify-out/snapshots/v1/93216e139922f2f954f718126c932b27440b938e67a1d66488ee48b42ed07673/d4d6ba275f3d6291636692f4746135f022cea15aa777ae520d6dc2ee0ab75b53/cost.json # graphify-out/snapshots/v1/93216e139922f2f954f718126c932b27440b938e67a1d66488ee48b42ed07673/d4d6ba275f3d6291636692f4746135f022cea15aa777ae520d6dc2ee0ab75b53/graph.json # graphify-out/snapshots/v1/93216e139922f2f954f718126c932b27440b938e67a1d66488ee48b42ed07673/d4d6ba275f3d6291636692f4746135f022cea15aa777ae520d6dc2ee0ab75b53/manifest.json
|
Lopu review — PR #662 (head No failing checks: build + typecheck ratchet + unit tests, the API suite, CodeQL and GitGuardian all pass, and the trusted CodeQL snapshot for this head is empty, so there are no alerts to fix or dispose. What holds upI checked the CSS fix against the native mask rather than the description. The native clipboard guard is right for the reason the comment gives — clipboard observers latch on One thing worth calling out that the description undersells: One fix appliedReturn could not activate the new recovery buttons. The window-level if (event.key === 'Enter' && selected) { event.preventDefault(); void runAction(defaultAction); }It listens on I let a focused recovery control keep its native Return, scoped to unmodified Validation: 9 emoji tests pass (8 existing + 1 new); full Notes, no change requested
The diagnosis behind this PR is aimed at causes rather than symptoms, and with the Return fix the recovery path now works by keyboard as well as by pointer. No further blocking concerns from me. — Lopu |
🤖 Lopu review — PR #662 (head
|
| Check | Result |
|---|---|
vitest run src/components/EmojiPicker.test.tsx |
9/9 passed |
vitest run (full @commander/desktop-ui) |
81/81 passed, 13 files |
tsc -p tsconfig.json --noEmit |
clean |
vite build |
succeeded |
Swift tests are macOS-only and could not run on this Linux runner; I reviewed testDeniedEmojiPasteDoesNotTouchTheClipboard by hand instead — the injected isAccessibilityTrusted: seam, the argument order against the defaulted pinned:/pinningEnabled: parameters, and the changeCount assertion are all correct, and shutdown() exists.
Why the fix is sound
The native change is the important one, and it is right. Previously the keep-clipboard path snapshotted, cleared and wrote the pasteboard before checking AXIsProcessTrusted(), then restored on failure. Restoration does not undo the write from a clipboard observer's point of view, and it bumps changeCount twice. Hoisting trusted and returning early for preserveClipboard when untrusted, target-less, or target-terminated means a denied keep-clipboard paste now touches the pasteboard exactly zero times. I traced every branch of the rewritten paste(_:preserveClipboard:) and each one still produces a coherent (copied, pasted, requiresAccessibility) triple:
- untrusted +
paste-and-copystill deliberately falls through, writes the clipboard, and reportscopied: true, pasted: false, requiresAccessibility: true— correct, because the copy half genuinely succeeded; requiresAccessibilitynow defaults to!trustedrather thanfalse, which is strictly more accurate and does not contradict the later explicit assignment;if preserveClipboard, !trusted || pasteTarget == nil || pasteTarget?.isTerminated == trueparses as intended, and thepasteTarget == nilclause makes the optional-chained comparison unambiguous.
NativePasteResult is unchanged and EmojiPicker.tsx is its only consumer, so there is no API-compatibility exposure. The application.open recovery URL passes the bridge's validator: x-apple.systempreferences is a legal RFC 3986 scheme (. is permitted), so URL(string:) yields a non-nil scheme and the file-path fallback is never taken. The value is a hardcoded constant, so it adds no new attack surface beyond what ExtensionsSettings/AccountSettings already pass through that same method.
On the UI side, moving remember/learn behind an explicit recordSuccess() is the correct shape: a rejected paste no longer mutates recents or learned ranking. The one intentional exception — if (result.copied && !preserveClipboard) recordSuccess() — is right, since for paste-and-copy the copy really did happen. The interleaved setStatus(null) inside recordSuccess() followed by the real setStatus(...) is safe: React 19 batches these even after an await, so the later value always wins. learnedSelectionID is set before remember/learn so the selection-restore effect reliably re-finds the emoji after the grid reorders.
On the CSS, grid-template-columns: minmax(0, 1fr) plus the min-width: 0 chain is the correct fix for the reported overflow — the implicit auto column was previously sized by the footer's min-content width. One specificity detail I checked because the comment doesn't mention it: .launcher-shell:has(> .emoji-picker-panel) is (0,2,0), so it beats the @media (max-width: 720px) rule's (0,1,0) regardless of source order, but it is correctly still lost to :root[data-window-mode='compact'] .launcher-shell at (0,3,0). Compact mode therefore keeps its deliberate 12px inset instead of being forced to 18px. That is the behaviour you want; it just isn't obvious from the comment.
The new tests are genuinely good — the changeCount assertion, the in-flight serialization test with a deferred promise, and the "denied double clicks don't reorder the unfiltered grid" test all pin the actual regressions rather than the implementation.
One non-blocking observation
Not worth a commit on your branch, and I have not changed it — but recording it since I verified it rather than guessed it.
When Accessibility is denied, a keyboard user tabs to Copy Emoji and presses Return. recordSuccess() sets requiresAccessibility to false, which unmounts .emoji-recovery-actions — including the button that currently holds focus. I confirmed with a throwaway probe (run, then deleted; the worktree is clean) that document.activeElement becomes <body>:
LOPU_PROBE activeElement = BODY "" isBody = true
The user is not stranded, because arrows / Return / Escape are all handled on window and keep working from body. The only real cost is that a subsequent Tab restarts from the top of the panel. If you ever want to tidy it, returning focus to the search input inside recordSuccess() when the recovery controls were mounted would close it. Entirely optional.
Relatedly, the catch branch does not clear requiresAccessibility, so a later unrelated failure (e.g. Clipboard access is unavailable) renders under the Accessibility help text. I looked at changing it and decided against: when that state was reached, Accessibility genuinely is still required, so leaving the guidance up is the more truthful behaviour.
Housekeeping
I could not enumerate which other open PRs overlap EmojiPicker.tsx / app.css — api.github.com/graphql returned HTTP 502 while I was querying. That is a transient GitHub outage, not a signal about this branch; the existing lopu: overlapping files label still stands on its own.
The PRs/662-…md investigation note, both TESTING.md updates and the remix/CHANGELOG.md entry all follow the repository's existing conventions, and the manual-QA checklist additions are specific enough to actually be executed.
Nice work — this is a clean, well-scoped fix with the native and web halves properly aligned.
— Lopu, automated repository review
|
Lopu review — PR #662 (head The failing checks here are not yours
CodeQL: no open alerts on this head, so nothing to fix or dispose. The codeThe core changes are correct and nicely targeted.
The four new denied-paste tests earn their keep, particularly the deferred-promise one for in-flight serialization. One fix applied
While verifying, I scripted a resolve of every Non-blockingThe permission help text and recovery buttons sit outside the ValidationRun in the PR worktree at
Note Looks good to merge. The clipboard-safety fix is the security-relevant part and it's done correctly. |
|
Lopu review — PR #662 Compared head What held up under reviewI traced every exit path of the reworked Swift
One defect, fixed in this branchActivating Copy Emoji succeeds, clears Confirmed before changing anything, via a throwaway probe: The fix records focus entering Validation
Negative control: with the new effect body replaced by an early
Left alone deliberately
Nice work on the root-cause note — it matches what the code actually does, and dependency-injecting |
|
Lopu review — approving on the merits, no changes made. I compared Checks / CodeQL: all green — no failing, cancelled or timed-out check. The long list of What I actually verified, beyond reading the diff:
Validation I ran here: 11/11 Three observations I deliberately did not change:
The outstanding Touch ID step for the Accessibility grant refresh is a local permission migration and gates nothing in CI. |
🤖 Lopu live PR updateStatus: ✅ Lopu finished — this PR is mergeable Current phase: GitHub verified the published branch result Estimated completion: Done — no further active-work ETA. Time conversion (UTC source)
Los Angeles and Melbourne use their real IANA time zones, so PDT/PST and AEST/AEDT offsets change automatically. Lopu queue and PR pulse
Related PR context
Exact branch pair: Timeline
Technical run details — optional; this comment is the human-facing source of truth. |
|
🤝 Merged No AI resolution was needed by merge time; the branch was updated with a plain merge commit.
Please review the merge commit before relying on it. |
Lopu-Conflict-Resolution: run=34088829197 pr=662 # Conflicts: # graphify-out/snapshots/v1/94433d17c176f2bdac5c0a3e90f71417ae106a476d7bd861d5d9b811b7fa25aa/a72fe54e341fcdc9153402f7ce761c58d7f7269bd5a972f94ac68e85ffc25a27/GRAPH_REPORT.md # graphify-out/snapshots/v1/94433d17c176f2bdac5c0a3e90f71417ae106a476d7bd861d5d9b811b7fa25aa/a72fe54e341fcdc9153402f7ce761c58d7f7269bd5a972f94ac68e85ffc25a27/cost.json # graphify-out/snapshots/v1/94433d17c176f2bdac5c0a3e90f71417ae106a476d7bd861d5d9b811b7fa25aa/a72fe54e341fcdc9153402f7ce761c58d7f7269bd5a972f94ac68e85ffc25a27/graph.json # graphify-out/snapshots/v1/94433d17c176f2bdac5c0a3e90f71417ae106a476d7bd861d5d9b811b7fa25aa/a72fe54e341fcdc9153402f7ce761c58d7f7269bd5a972f94ac68e85ffc25a27/manifest.json # graphify-out/snapshots/v1/f1ba09fc341f66c1eb80e7e61e4d5bb6d95de1cd339228d588354f88ed3d2f20/412c079b5a0af164a118dd52e28a0faed6b07903eb7e9bea2b9f5ae0f3dcb804/GRAPH_REPORT.md # graphify-out/snapshots/v1/f1ba09fc341f66c1eb80e7e61e4d5bb6d95de1cd339228d588354f88ed3d2f20/412c079b5a0af164a118dd52e28a0faed6b07903eb7e9bea2b9f5ae0f3dcb804/cost.json # graphify-out/snapshots/v1/f1ba09fc341f66c1eb80e7e61e4d5bb6d95de1cd339228d588354f88ed3d2f20/412c079b5a0af164a118dd52e28a0faed6b07903eb7e9bea2b9f5ae0f3dcb804/graph.json # graphify-out/snapshots/v1/f1ba09fc341f66c1eb80e7e61e4d5bb6d95de1cd339228d588354f88ed3d2f20/412c079b5a0af164a118dd52e28a0faed6b07903eb7e9bea2b9f5ae0f3dcb804/manifest.json # graphify-out/snapshots/v1/f7e629902b574fb823945c6fdf643102dc6d2d2a45d808e682ff1d6e0309ad29/9fdf09c60851ff3f5884a59af3930c059b5c85d70b1265a35aab688e61ae68c0/GRAPH_REPORT.md # graphify-out/snapshots/v1/f7e629902b574fb823945c6fdf643102dc6d2d2a45d808e682ff1d6e0309ad29/9fdf09c60851ff3f5884a59af3930c059b5c85d70b1265a35aab688e61ae68c0/cost.json # graphify-out/snapshots/v1/f7e629902b574fb823945c6fdf643102dc6d2d2a45d808e682ff1d6e0309ad29/9fdf09c60851ff3f5884a59af3930c059b5c85d70b1265a35aab688e61ae68c0/graph.json # graphify-out/snapshots/v1/f7e629902b574fb823945c6fdf643102dc6d2d2a45d808e682ff1d6e0309ad29/9fdf09c60851ff3f5884a59af3930c059b5c85d70b1265a35aab688e61ae68c0/manifest.json
Structural `graphify update` completed (graphify 0.9.4, no semantic credential available). Refreshed by the resolve-pr-conflicts workflow: https://github.com/lopugit/thingtime/actions/runs/34088829197 Lopu-Conflict-Resolution: run=34088829197 pr=662
🤖 Lopu live PR updateStatus: ✅ Lopu finished — this PR is mergeable Current phase: GitHub verified the published branch result Estimated completion: Done — no further active-work ETA. Time conversion (UTC source)
Los Angeles and Melbourne use their real IANA time zones, so PDT/PST and AEST/AEDT offsets change automatically. Lopu queue and PR pulse
Related PR context
Exact branch pair: Timeline
Technical run details — optional; this comment is the human-facing source of truth. |
|
🤝 Merged No AI resolution was needed by merge time; the branch was updated with a plain merge commit.
Please review the merge commit before relying on it. |
Lopu-Conflict-Resolution: run=34097654441 pr=662
Structural `graphify update` completed (graphify 0.9.4, no semantic credential available). Refreshed by the resolve-pr-conflicts workflow: https://github.com/lopugit/thingtime/actions/runs/34097654441 Lopu-Conflict-Resolution: run=34097654441 pr=662
|
Lopu review — PR #662 (head Approving on the merits. I made no changes — nothing in the diff justified one. No failing checks. Everything non-skipped on this head is green (CodeQL, GitGuardian, build + typecheck ratchet + unit tests, the headless API suite, contract advisories). The I re-derived the PR's own contribution: 8 source/doc files plus regenerated Graphify output. Everything else in the range arrived through the two What holds up
Validation I ran in the PR worktree
I could not exercise the Swift target: this reviewer runs on Linux and Two non-blocking notes
— Lopu 🤖 |
🤖 Lopu live PR updateStatus: ✅ Lopu finished — this PR is mergeable Current phase: GitHub verified the published branch result Estimated completion: Done — no further active-work ETA. Time conversion (UTC source)
Los Angeles and Melbourne use their real IANA time zones, so PDT/PST and AEST/AEDT offsets change automatically. Lopu queue and PR pulse
Related PR context
Exact branch pair: Timeline
Technical run details — optional; this comment is the human-facing source of truth. |
|
🤝 Merged No AI resolution was needed by merge time; the branch was updated with a plain merge commit.
Please review the merge commit before relying on it. |
Lopu-Conflict-Resolution: run=34121206780 pr=662 # Conflicts: # graphify-out/snapshots/v1/94433d17c176f2bdac5c0a3e90f71417ae106a476d7bd861d5d9b811b7fa25aa/a72fe54e341fcdc9153402f7ce761c58d7f7269bd5a972f94ac68e85ffc25a27/GRAPH_REPORT.md # graphify-out/snapshots/v1/94433d17c176f2bdac5c0a3e90f71417ae106a476d7bd861d5d9b811b7fa25aa/a72fe54e341fcdc9153402f7ce761c58d7f7269bd5a972f94ac68e85ffc25a27/cost.json # graphify-out/snapshots/v1/94433d17c176f2bdac5c0a3e90f71417ae106a476d7bd861d5d9b811b7fa25aa/a72fe54e341fcdc9153402f7ce761c58d7f7269bd5a972f94ac68e85ffc25a27/graph.json # graphify-out/snapshots/v1/94433d17c176f2bdac5c0a3e90f71417ae106a476d7bd861d5d9b811b7fa25aa/a72fe54e341fcdc9153402f7ce761c58d7f7269bd5a972f94ac68e85ffc25a27/manifest.json # graphify-out/snapshots/v1/984082b969db496ff66401bac879fdb80deaaf6e9bbcbca737c0a5b151cc3df8/3a0fc01eb858b55489f2a370c4441d91c18d46721a15d509de55783b4c5ee522/GRAPH_REPORT.md # graphify-out/snapshots/v1/984082b969db496ff66401bac879fdb80deaaf6e9bbcbca737c0a5b151cc3df8/3a0fc01eb858b55489f2a370c4441d91c18d46721a15d509de55783b4c5ee522/cost.json # graphify-out/snapshots/v1/984082b969db496ff66401bac879fdb80deaaf6e9bbcbca737c0a5b151cc3df8/3a0fc01eb858b55489f2a370c4441d91c18d46721a15d509de55783b4c5ee522/graph.json # graphify-out/snapshots/v1/984082b969db496ff66401bac879fdb80deaaf6e9bbcbca737c0a5b151cc3df8/3a0fc01eb858b55489f2a370c4441d91c18d46721a15d509de55783b4c5ee522/manifest.json # graphify-out/snapshots/v1/e3ba3c8ae982a537c4b89f873a52ca34486a7ca5b08d6b83eba889cbfef81466/61963b9eb04328ad6ca8f0cbd8eb52c2bf299b7a113ededa9faf069c8caed05e/GRAPH_REPORT.md # graphify-out/snapshots/v1/e3ba3c8ae982a537c4b89f873a52ca34486a7ca5b08d6b83eba889cbfef81466/61963b9eb04328ad6ca8f0cbd8eb52c2bf299b7a113ededa9faf069c8caed05e/cost.json # graphify-out/snapshots/v1/e3ba3c8ae982a537c4b89f873a52ca34486a7ca5b08d6b83eba889cbfef81466/61963b9eb04328ad6ca8f0cbd8eb52c2bf299b7a113ededa9faf069c8caed05e/graph.json # graphify-out/snapshots/v1/e3ba3c8ae982a537c4b89f873a52ca34486a7ca5b08d6b83eba889cbfef81466/61963b9eb04328ad6ca8f0cbd8eb52c2bf299b7a113ededa9faf069c8caed05e/manifest.json
Structural `graphify update` completed (graphify 0.9.4, no semantic credential available). Refreshed by the resolve-pr-conflicts workflow: https://github.com/lopugit/thingtime/actions/runs/34121206780 Lopu-Conflict-Resolution: run=34121206780 pr=662
✅ Develop S3 preview ready
8a852483developThe alias passed the develop bucket CORS preflight and a final live PR/SHA fence.
Generic Vercel Preview deployments use the shared development runtime; this controller adds the stable exact-SHA alias and marker-scoped cleanup.
Denied emoji paste stretched the picker content beyond its fixed native window and reordered recents despite inserting nothing. Constrain the emoji grid to its available width, keep failed paste attempts out of recents/learning, serialize pending actions, and show readable Accessibility recovery and explicit Copy Emoji controls.
The native keep-clipboard path now checks trust and target availability before touching the pasteboard. The observed enabled-but-denied grant belongs to the earlier Apple Development signature while the installed app uses Developer ID; macOS explicitly logged a code-requirement mismatch. Preserve the current signing identity and refresh only Commander's existing grant with the user's approval.
Validation: 205 TypeScript/JavaScript tests, 68 Rust tests and 28 Swift tests passed; full typecheck and builds passed. The final retry-feedback adjustment passed all eight focused emoji tests and the UI typecheck/build again.
The installed build uses source revision
be86c3c6fab59898e28473d4bc79df1e41a9d630; the final PR commit adds only investigation notes and Graphify outputs. The bundle is Developer ID signed, notarized, stapled and verified, with its designated requirement unchanged across rebuilds. One installed host and its healthy daemon are running. Native visual checks cover normal and minimum window sizes, denied-paste feedback, the Actions menu and scrolling to the end of the results. Chrome rejected the local QA page under its URL policy; no workaround was attempted.Successful-paste acceptance remains pending the user's Touch ID authentication for the explicitly approved Commander Accessibility grant refresh. System Settings still displays that authentication prompt; no other app's permissions have been changed. This is a local permission migration, not a CI or code-signing failure.
Graphify structural and semantic refresh completed through the local proxy, including portable outputs. Existing cross-source semantic identifier collisions are documented in the investigation note. No Vercel preview was published: preview authorization/publish checks are skipped for this native-app change.