Skip to content

feat(location): custom pin with Maps URL parsing + restore on trail stop#54

Open
jiunshinn wants to merge 3 commits into
EvanBacon:mainfrom
jiunshinn:feat/location-pin-with-restore
Open

feat(location): custom pin with Maps URL parsing + restore on trail stop#54
jiunshinn wants to merge 3 commits into
EvanBacon:mainfrom
jiunshinn:feat/location-pin-with-restore

Conversation

@jiunshinn

@jiunshinn jiunshinn commented May 13, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Pin Location — paste-and-go input added to the Location panel. Accepts raw `lat,lng`, full Google/Apple Maps long-form URLs, and short links (`maps.app.goo.gl`, `goo.gl`, `maps.apple.com`). Short links are resolved server-side via the existing `/exec` channel since browsers can't read cross-origin redirect targets.
  • Restore on stop — pressing Stop after a trail playback now returns the simulator to the user's last manual pin (or `clear` if no pin was ever set). The previous behaviour captured the trail's first point as the origin, so pinning Tokyo then playing the Apple Park trail would land you in Cupertino on Stop instead of back at Tokyo.

Bug repro (against `main` before this PR)

  1. Open the Location panel → expand Pin Location.
  2. Set a custom pin (e.g. `35.6586, 139.7454`).
  3. Pick a preset trail (e.g. Apple Park Loop), press Play, wait a few seconds, press Stop.
  4. Observe: simulator stays near Apple Park. Expected: returns to Tokyo.

The bug only manifests with a pin set; no-pin users get the same `clear` behaviour as before.

Implementation

File Change
`client/location-input-parser.ts` (new) Pure parser. Returns `coords` / `redirect` / `error`.
`client/location-session.ts` (new) Pure helpers: `pickSessionOrigin`, `buildEndSessionCommand`. Encodes the decision that the origin to restore is the user's last manual pin, not a trail point.
`client/location-emulation-tool.tsx` Adds `CustomLocationPin`. `LocationEmulationTool` owns `lastPinnedRef`; the pin component reports via `onPinned` so the parent can capture it as the session origin on next Play. Stop handler + unmount cleanup share `buildEndSessionCommand`.
`tests/location-input-parser.test.ts` (new) 25 tests covering coord parsing, Google/Apple long-form URLs, short-link detection, and `buildRedirectResolveCommand`.
`tests/location-session.test.ts` (new) 7 tests, including a regression guard that explicitly rejects a trail-start as the origin.

Test plan

  • `bun test packages/serve-sim/src/tests` — 117 pass, 0 fail
  • `tsc --noEmit` — clean
  • `bun run packages/serve-sim/build.ts` — clean
  • Manual: pin Tokyo → play Apple Park trail → press Stop → simulator returns to Tokyo
  • Manual: no pin → play trail → Stop → simulator location is cleared

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Custom location pin UI: accept raw coordinates, map URLs, and short links; previews parsed location and sets simulator location.
    • Improved session handling to capture and restore the pinned origin and to clear location when appropriate.
  • Tests

    • Extensive test coverage for input parsing, map URL forms, redirect resolution, session origin selection, and command generation/escaping.

Review Change Stack

jiunshinn and others added 2 commits May 13, 2026 17:55
Adds a pure parser used by the location pin UI. Accepts raw `lat,lng`,
full Google/Apple Maps long-form URLs, and short links (maps.app.goo.gl,
goo.gl, maps.apple.com) — short links return a `redirect` result so the
caller can follow the redirect server-side via curl (browsers can't read
cross-origin redirect targets). Also exports `buildRedirectResolveCommand`
so the caller doesn't have to hand-craft the curl invocation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds `CustomLocationPin` to LocationEmulationTool — a paste-and-go input
that accepts raw lat,lng or any Google/Apple Maps URL. Short links are
resolved server-side via the existing /exec channel.

Fixes a session-origin bug introduced when the pin feature is in play:
on `Play`, the old code captured the *trail's* first point as the
"return-here-on-stop" location. So pinning Tokyo then playing the Apple
Park trail and pressing Stop would land you in Cupertino, not back at
Tokyo. The simulator's only off-trail location source is the user's
manual pin, so that's what we capture and restore now.

Implementation:
  - New `location-session.ts` with `pickSessionOrigin` and
    `buildEndSessionCommand` pure helpers (tested in isolation).
  - `LocationEmulationTool` owns a `lastPinnedRef`; `CustomLocationPin`
    calls back via `onPinned` so the parent learns about pin events.
  - On play, capture from `lastPinnedRef` (not the trail point). On a
    new pin, clear the captured origin so next play re-captures.
  - Stop handler and unmount cleanup share `buildEndSessionCommand`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented May 13, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9d0068ab-61f6-4754-90c7-deca8c4f56b0

📥 Commits

Reviewing files that changed from the base of the PR and between 117bf23 and 17b6750.

📒 Files selected for processing (1)
  • packages/serve-sim/src/client/location-emulation-tool.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/serve-sim/src/client/location-emulation-tool.tsx

📝 Walkthrough

Walkthrough

This PR adds a CustomLocationPin UI to LocationEmulationTool, a parser for raw coordinates and Maps URLs with redirect resolution, session-origin helpers to pick/restore simulator location, and integrates these to pause playback when pinning and restore/clear location on stop.

Changes

Custom Location Pin Feature

Layer / File(s) Summary
Session origin type and helpers
packages/serve-sim/src/client/location-session.ts, packages/serve-sim/src/__tests__/location-session.test.ts
SimLocation type and pickSessionOrigin/buildEndSessionCommand helpers manage session origin selection and generate xcrun simctl location commands (coordinates formatted to 7 decimals or cleared).
Location input parsing module
packages/serve-sim/src/client/location-input-parser.ts, packages/serve-sim/src/__tests__/location-input-parser.test.ts
parseLocationInput parses raw lat,lng, Google Maps and Apple Maps URL formats, returns {kind: "coords"}, {kind: "redirect"} for short-links or coord-missing URLs, or {kind: "error"}. buildRedirectResolveCommand emits a curl command that follows redirects and safely escapes single quotes. Tests exercise many URL forms and redirect/escaping behavior.
Tool integration with CustomLocationPin component
packages/serve-sim/src/client/location-emulation-tool.tsx
Add sessionOriginRef and lastPinnedRef; capture session origin via pickSessionOrigin; replace inline end-session command construction with buildEndSessionCommand; add CustomLocationPin component that previews parsed input, resolves redirects via exec when needed, pauses playback before pinning, and records pinned coordinates.

Sequence Diagram

sequenceDiagram
  participant User
  participant CustomLocationPin
  participant LocationEmulationTool
  participant parseLocationInput
  participant buildRedirectResolveCommand
  participant buildEndSessionCommand
  participant Simulator

  User->>CustomLocationPin: Enter lat,lng or Maps URL
  CustomLocationPin->>parseLocationInput: Parse input
  alt Direct coordinates
    parseLocationInput-->>CustomLocationPin: {kind: "coords", lat, lng}
    CustomLocationPin->>Simulator: xcrun simctl location set
  else Redirect needed
    parseLocationInput-->>CustomLocationPin: {kind: "redirect", url}
    CustomLocationPin->>buildRedirectResolveCommand: Resolve short link
    buildRedirectResolveCommand-->>CustomLocationPin: Final URL
    CustomLocationPin->>parseLocationInput: Re-parse final URL
    parseLocationInput-->>CustomLocationPin: {kind: "coords", lat, lng}
    CustomLocationPin->>Simulator: xcrun simctl location set
  end
  
  CustomLocationPin->>LocationEmulationTool: onBeforePin (pause playback)
  CustomLocationPin->>LocationEmulationTool: onPinned (save to lastPinnedRef)
  
  User->>LocationEmulationTool: Stop playback
  LocationEmulationTool->>buildEndSessionCommand: Generate restore command
  buildEndSessionCommand-->>Simulator: xcrun simctl location set/clear
Loading

🎯 3 (Moderate) | ⏱️ ~25 minutes

🐰 A pinned location here, a parsed Maps URL there,
Session origins saved with customary care,
CustomLocationPin paints the UI bright,
Coordinates set with redirection's might!
Now hop along, the feature's in sight! 🗺️

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 63.64% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main features: custom pin with Maps URL parsing and location restoration on trail stop, which aligns with the primary changes across all modified files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/serve-sim/src/client/location-session.ts (1)

21-25: 💤 Low value

Consider whether this wrapper function adds sufficient value.

The pickSessionOrigin function is trivially simple—it just returns its input. While the comments explain this is for testability and to document the bug fix, a direct reference to lastPinnedRef.current at call sites might be clearer. That said, if the abstraction helps with testing and documentation, this is defensible.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/serve-sim/src/client/location-session.ts` around lines 21 - 25,
pickSessionOrigin is a trivial pass-through wrapper around the lastPinned value
and adds little value; remove the unused abstraction by deleting the
pickSessionOrigin function and update all call sites to reference
lastPinnedRef.current (or the actual SimLocation variable) directly, and adjust
any tests or mocks that referenced pickSessionOrigin to use the direct value or
mock lastPinnedRef.current instead so behavior and testability remain intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/serve-sim/src/client/location-emulation-tool.tsx`:
- Around line 447-454: The onPinned handler currently sets lastPinnedRef.current
but clears sessionOriginRef.current, causing buildEndSessionCommand to receive
null and clear the simulator location; change the handler in onPinned (the arrow
function that receives (lat, lng)) to update sessionOriginRef.current = { lat,
lng } instead of setting it to null (so Stop restores the last manual pin),
leaving lastPinnedRef.current assignment intact; alternatively, if you prefer to
end the trail immediately, set the status to 'idle' there (update the same
onPinned handler) so the session ends when a user pins.

---

Nitpick comments:
In `@packages/serve-sim/src/client/location-session.ts`:
- Around line 21-25: pickSessionOrigin is a trivial pass-through wrapper around
the lastPinned value and adds little value; remove the unused abstraction by
deleting the pickSessionOrigin function and update all call sites to reference
lastPinnedRef.current (or the actual SimLocation variable) directly, and adjust
any tests or mocks that referenced pickSessionOrigin to use the direct value or
mock lastPinnedRef.current instead so behavior and testability remain intact.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a653a4eb-7d92-488b-a078-b62f5a4e43d4

📥 Commits

Reviewing files that changed from the base of the PR and between 84322fd and 117bf23.

📒 Files selected for processing (5)
  • packages/serve-sim/src/__tests__/location-input-parser.test.ts
  • packages/serve-sim/src/__tests__/location-session.test.ts
  • packages/serve-sim/src/client/location-emulation-tool.tsx
  • packages/serve-sim/src/client/location-input-parser.ts
  • packages/serve-sim/src/client/location-session.ts

Comment thread packages/serve-sim/src/client/location-emulation-tool.tsx
If the user pinned a location while a trail was playing or paused,
onPinned cleared the captured session origin. A subsequent Stop then
ran `simctl location clear` and wiped the pin the user had just set.
Pinning is itself the user setting a "where I am" — make that pin the
session origin so Stop restores to it.

Caught by CodeRabbit review on EvanBacon#54.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jiunshinn

Copy link
Copy Markdown
Contributor Author

Thanks for the review @coderabbitai — pushed 17b6750.

Actionable comment (fixed): you're right, the onPinned handler clearing sessionOriginRef was a real bug. Concrete repro on the previous commit: Play trail → Pause → Pin Tokyo → Stop ran simctl location clear, wiping the just-set Tokyo pin. Now onPinned writes the pin into both lastPinnedRef and sessionOriginRef, so Stop restores to the pin in every case (including mid-pause pin).

Nit on pickSessionOrigin (declined, keeping it): trivial as a one-liner, but it's the fallback path for the Pin → Play → Stop → Play → Stop sequence — after the first Stop clears sessionOriginRef, the second Play re-captures via pickSessionOrigin(lastPinnedRef.current). The helper also anchors a regression test that explicitly rejects substituting a trail start for the user's pin, which is the original bug this PR fixes. Cost is a 3-line function; value is one named place where the design rule lives.

@coderabbitai

coderabbitai Bot commented May 13, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

@jiunshinn Thanks for the clear walkthrough!

The fix for the onPinned handler makes sense — writing the pin into both lastPinnedRef and sessionOriginRef correctly covers the mid-pause pin case, and the concrete repro (Play → Pause → Pin → Stop → unexpected clear) is a good illustration of why the previous approach was wrong.

Totally on board with keeping pickSessionOrigin. The Pin → Play → Stop → Play → Stop re-capture sequence and the regression test it anchors are compelling justifications — having one named home for that design rule is worth the 3 lines.


✏️ Learnings added
Learnt from: jiunshinn
URL: https://github.com/EvanBacon/serve-sim/pull/54

Timestamp: 2026-05-13T21:22:15.806Z
Learning: In `packages/serve-sim/src/client/location-session.ts`, `pickSessionOrigin` is intentionally kept as a standalone helper (not inlined) because it anchors the fallback path for the `Pin → Play → Stop → Play → Stop` sequence: after the first Stop clears `sessionOriginRef`, the second Play re-captures the user's manual pin via `pickSessionOrigin(lastPinnedRef.current)`. It also anchors a regression test that explicitly guards against substituting a trail start for the user's pin. This design rule should not be simplified away.

Learnt from: jiunshinn
URL: https://github.com/EvanBacon/serve-sim/pull/54

Timestamp: 2026-05-13T21:22:15.806Z
Learning: In `packages/serve-sim/src/client/location-emulation-tool.tsx`, the `onPinned` handler must write the pin coordinates into **both** `lastPinnedRef` and `sessionOriginRef`. Writing only to `lastPinnedRef` causes a bug: `Play trail → Pause → Pin location → Stop` incorrectly runs `simctl location clear` instead of restoring to the pinned location. Fix was introduced in commit `17b6750`.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant