Skip to content

feat(remote): choose a pairing address and prefer Tailscale - #850

Merged
penso merged 13 commits into
mainfrom
worktree/remote-controle
Oct 28, 2026
Merged

penso merged 13 commits into
mainfrom
worktree/remote-controle

Conversation

@penso

@penso penso commented Oct 27, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • Prefer Tailscale for pairing and show the selected Tailscale-first design: a recommended card, expandable alternatives, daemon-host interface names, and network guidance.
  • Support mouse and keyboard selection plus remote pair --address HTTPS_ORIGIN. Preserve the selected origin across token rotation without changing the token or interrupting an in-flight poll. Fix the close icon.
  • Include backward-compatible address metadata in pairing offers and remote status; explain when an older development daemon needs restarting. Use the same interface evidence for labels and default QR ordering, so unrelated CGNAT addresses never displace LAN. Recognize Tailscale DNS names for custom certificates too.
  • Persist a certificate/key per IP, retaining the existing CA and migrating covered addresses without changing trusted certificate bytes. Select by TCP destination for IP clients without SNI; a new LAN address does not replace the Tailscale certificate. Save replacements before presenting them and exclude transient link-local addresses.
  • Scope Home Screen transfer tokens to the current browser pairing, clear stale tokens on re-pairing, serialize concurrent offers, and add direct QR-scanning recovery for unpaired installations.
  • Give the installed mobile app an opaque top edge, remove blur from the chat header, and open empty chats at the header with an agent icon and introductory text. Populated chats still follow the latest entry without pulling readers away from earlier messages.
  • Accept both valid initial streaming render shapes in the existing phone-stream regression. Merge main and resolve the duplicate automation test-fixture change using main's version.

QA

  • umask 022; just ci on macOS arm64, commit 12f29b0f11d8ab5ccfc178d08b0c5d1459479a7a — passed.
  • HERDR_LINT_CACHE=<isolated cache> just lint-linux — passed on the final commit (Linux ARM64 container). The initial shared-cache run reported stale protocol types; a fresh isolated build passed.
  • just lint-windows — passed on the final commit.
  • just check-commits and git diff origin/main...HEAD --check — passed.
  • node --test scripts/test-remote-chat-scroll.mjs scripts/test-remote-handoff.mjs — 10 passed during development; covers chat scrolling, pairing-scoped transfer reuse, re-pairing, and concurrent/stale offers.
  • cargo test --locked -p herdr-remote-control --all-features — 351 passed, 1 opt-in preview ignored, for the ranking fix. Includes per-IP persistence/migration/renewal, real no-SNI TLS handshakes, and CGNAT ranking regressions.
  • just test-build — release build and 14 CLI tests passed for the per-IP certificate implementation.
  • Local upgrade verification preserved the Tailscale IPv4/IPv6 certificate bytes in private 0600 per-IP files; live no-SNI loopback TLS returned the unchanged certificate.
  • Focused development tests: 14 dialog tests, 44 web-render tests, and 8 web-route tests passed. just web-css regenerated the committed stylesheet; node --check crates/herdr-remote-control/src/web/assets/app.js passed.
  • Inspected the actual native macOS pairing dialog in light and dark themes, collapsed and expanded, with long IPv6 selection and a 640px-wide window. Used a temporary synthetic-offer fixture through the isolated native harness; the temporary fixture changes were removed.
  • Generated the empty-chat preview from the actual Rust templates and inspected it in an isolated 500px-wide desktop Chromium window.
  • Pair a phone over Tailscale from a different network, then verify LAN pairing on the same network.
  • Switch addresses on a phone pairing, wait for token rotation, and verify the selected origin is retained; install the PWA from the stable Tailscale origin.
  • On an installed iOS PWA, check the top edge and chat header after reopening, empty-chat entry, keyboard/composer layout, and scrolling populated conversations. Desktop previews do not prove iOS system-blur behavior.
  • Re-pair in Safari, reinstall the Home Screen app, and verify a fresh transfer token is used; also exercise direct QR-scanning recovery.

Screenshots

After screenshots use synthetic data and an inactive QR. A native before screenshot was not captured.

Native pairing: light Native pairing: dark
Light pairing Dark pairing
Narrow native window and empty-chat browser preview

Narrow native pairing

Empty chat, desktop browser preview

penso added 3 commits October 26, 2026 23:13
Prefer Tailscale origins so pairing can work across networks, and let users select another advertised HTTPS origin in the pairing dialog or through remote pair --address. Preserve the token, deadline, and in-flight poll when switching addresses, and keep the choice across token rotation.

Cover address validation, wire compatibility, CLI parsing, QR updates, and bounded dialog layout. Use the existing close icon and make the streaming regression accept both valid initial render shapes.
The all-feature CI run exposed an intermittent false result from the fake Herdr's server status check. Its script was written from a multithreaded test process, allowing concurrent forks to inherit writable descriptors and trigger Linux ETXTBSY.

Reuse the shared executable fixture writer, which writes in a single-threaded child before the script is launched. Remove the redundant platform-specific chmod helper from the Unix-only test module.
Scope the existing test-only Command::new allowance to the shared executable writer, matching other test crates without changing production process creation.
@penso

penso commented Oct 27, 2026 •

Copy link
Copy Markdown
Owner Author

Local validation: just ci ran locally and passed on commit 4d5edf255f535adc45ef6c61495f17c1a584ddca, macOS arm64. No extra recipes were run.

@greptile-apps

greptile-apps Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 5/5

[High impact] The reviewed changes appear safe to merge.

Summary

The PR adds Tailscale-first pairing choices and keeps each IP’s saved certificate separate.

  • Pairing offers put Tailscale first and let people choose the QR address.
  • The remote daemon keeps a separate certificate for each IP address.
  • The phone app ties transfer tokens to the current pairing and can scan a new QR.
  • Empty chats open at the header while active conversations follow new messages.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A[Current addresses] --> B[Network labels and ranking]
  B --> C[Pairing QR]
  C --> D[Phone connects]
  D --> E{DNS name supplied?}
  E -->|Yes| F[Saved DNS certificate]
  E -->|No| G[Saved certificate for destination IP]
Loading

Reviews (3) · Last reviewed commit: "fix(remote): rank pairing addresses usin..." · Reviewed by Greptile

Comment thread crates/herdr-gpui/src/remote/dialog.rs Outdated
penso added 5 commits October 27, 2026 14:24
Route remote-control dialog keys to address selection: Up/Down cycle the advertised origins and Home/End select the endpoints. Share selection and scrolling with clicks so the QR and visible row stay synchronized, while Escape closes the dialog and address keys cannot act after a phone claims it.

Cover the menu key route, QR updates, wraparound, long IPv6 rows in a narrow window, focus, mixed mouse and keyboard input, and claimed-pairing isolation.
Show the selected Tailscale-first design with expandable alternatives, daemon-host interface names, and network guidance. Preserve keyboard selection and scrolling, animate disclosure in both directions, and expose compatible metadata in pairing offers and remote status.

Explain when a development rebuild needs a daemon restart. Cover wire compatibility, interface classification, CLI output, and selection, and include native light, dark, and narrow screenshots using synthetic pairing data.
Remove the translucent iOS status-bar setting and give standalone web apps an opaque, theme-matched top edge so the system blur does not spill into the search field. Regenerate the shipped stylesheet and preserve safe-area spacing. On-device iOS verification remains outstanding.
# Conflicts:
#	crates/herdr-automate/src/testing.rs
Do not scroll empty conversations to the bottom of their safe-area and composer padding, either on entry or on stream updates. Keep following populated chats only when the reader is already at the end, and keep the opaque header below the status-bar safe area.

Add an icon-led empty state with distinct starting and stopped-agent messages. Cover the shipped scroll controller and rendered states, extend the browser preview fixture, and include a narrow browser screenshot.
@penso

penso commented Oct 27, 2026

Copy link
Copy Markdown
Owner Author

@greptile review

Comment thread crates/herdr-remote-control/src/tls/names.rs
penso added 2 commits October 27, 2026 17:14
Keep a private, atomic certificate file per IP, signed by the existing CA. Select IP certificates from the TCP destination when clients omit SNI, keep DNS and custom certificates separate, and migrate covered addresses without changing the certificate already trusted by phones. New LAN addresses no longer rotate Tailscale certificates; failed saves retain current identities.

Scope cached Home Screen transfer tokens to the current browser pairing, clear them when accepting a new key, and prevent concurrent or stale offers from crossing pairings. Add direct QR-scanning recovery, regression tests for persistence, migration, real TLS handshakes and transfer caching, and document the saved identity files.
Choose the default QR origin using the same Tailscale evidence as its network label. Remove blanket CGNAT priority from certificate-origin ordering so a carrier or unrelated VPN address cannot displace LAN. Prefer confirmed Tailscale IPv4 before its IPv6 alternative and retain MagicDNS recognition for custom certificates.

Cover LAN versus unrelated CGNAT, multiple adapters with confirmed Tailscale, and consistent DNS labels and ordering.
@penso

penso commented Oct 28, 2026

Copy link
Copy Markdown
Owner Author

@greptile review

penso and others added 3 commits October 27, 2026 21:25
…phone

The terminal screen draws each styled run as an inline span, and an inline
span paints its background over the font's content area only, not the
1.32 line height. At the fractional sizes the screen is fitted to, the
light terminal background showed between rows as thin horizontal stripes.
Making the runs inline blocks aligned to the top lets their background fill
the whole line box; a run never crosses a line, so nothing else changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BFavh7nWrdsE2snYJSQHaG
An agent given a long paste reports a title that is only its
`pasted_content` transport envelope, so tabs, sidebar rows, the tab
ghost, annotations, and the phone's chat list showed the wrapper instead
of the task. herdr-client gains `text::caption`, which unwraps a leading
envelope and rejects wrapper-only text, and `text::tab_label`, which falls
back to a pane, agent, or workspace caption. The snapshot itself stays
unchanged.

Orchestrator dispatch now names the new tab and pane explicitly with the
issue or PR identifier and title, so the paste never replaces that
context, and herdr-acp derives chat titles from the unwrapped prompt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BFavh7nWrdsE2snYJSQHaG
… the phone

Workspace headers in the phone app now carry the PR number in the same
tone as the desktop badge (ready, pending checks or review, blocked or
behind, attention, merged, draft or unknown), and tapping it opens the
pull request. The number also appears beside waiting questions and is
searchable.

Herdr UI publishes its existing pull request cache to the daemon over the
private control socket, resending a changed snapshot at once and an
unchanged one every 30 seconds, so the phone never receives GitHub
credentials and no second lookup starts. Updates reach open lists through
the patch stream. The daemon's status now reports its Herdr boot ID, and a
badge only shows when boot, workspace, directory, and branch all match, so
a recycled workspace cannot inherit another checkout's badge.
`herdr_github::pull_request::Tone` gains serde, with unknown tones read as
muted for forward compatibility.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BFavh7nWrdsE2snYJSQHaG
@penso
penso merged commit f9ec357 into main Oct 28, 2026
28 checks passed
@penso
penso deleted the worktree/remote-controle branch October 28, 2026 06:30
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