# dial — v2 Plan

Written against the v1 REPORT.md handoff. v1 is complete, tested, and out of
scope to modify except where noted. This plan is scoped to be handed to
Claude Code as-is; every judgment call is flagged so it can be overridden.

---

## 1. Alphabetical sort (small, do first)

- Add a third sort mode to the existing `s` toggle: **recently used → most
  used → alphabetical → (back to recently used)**.
- Alphabetical sorts by `Host` alias (the thing the user actually types/sees
  first in the list), case-insensitive.
- Favourites still pin to the top regardless of sort mode (unchanged v1
  behaviour).
- Persist the selected mode the same way recents/frequency already persist
  (`~/.ssh/.dial/data.json`).

No open questions here — this is a straightforward extension of existing code.

---

## 2. SSH key generation tool (`dial keygen`)

Rebuilt from the logic in `create_ssh_keypair.sh`, fixing the cross-platform
and UX issues found in it, and wired into `dial` as a new subcommand rather
than folded into the interactive picker.

**Why a separate subcommand, not part of the picker:** the picker's core
read-only-config guarantee is a deliberate v1 property. Keygen necessarily
*writes* to `~/.ssh/config` and creates key files, so it should be a distinct,
explicitly-invoked action (`dial keygen`) — not something that can happen by
accidentally pressing a key while browsing hosts.

### Fixes over the original script
- Replace the macOS-only `sed -i ''` public-key-comment logic with something
  that works identically on macOS/Linux/Windows (Go string handling, no shell
  `sed` dependency at all).
- Default algorithm: **ed25519**. Offer `--rsa` (4096-bit) as an explicit
  opt-out for hosts that don't support ed25519 (some older network gear, e.g.
  legacy broadcast equipment, may not).
- **Keep the `.pub` file.** Don't delete it after displaying it once — that
  behaviour in the original script means a lost copy-paste = a wasted key.
  Still print it to the terminal for convenience, but leave the file in place.
- Proper quoting/escaping when appending to `~/.ssh/config` (avoid the blind
  `read` + string-concat approach in the original).

### Proposed flow
```
dial keygen
  → prompt: key name / label
  → prompt: algorithm (default ed25519, flag for rsa)
  → generate into ~/.ssh/<label>  (and .pub alongside, kept)
  → prompt: add a Host entry to ~/.ssh/config now? (y/n)
      → if yes: Host alias / HostName / User / Port (default 22) / tag (optional, using existing `# tag:` convention)
  → print the public key once for easy copy, key file left on disk
```

**Assumption flagged:** this reuses `dial`'s existing static-list, low-light-
friendly prompt styling rather than plain unstyled `read` prompts like the
original script, for consistency with the rest of the tool.

---

## 3. Remaining v2 features (from REPORT.md open questions)

Going through the report's nine open questions individually, with a decision
made and flagged for each so this can go straight to Claude Code.

### 3.1 SFTP / file transfer mode — **in scope for v2: full in-app browser**
- After selecting a host, a second action (`t` for transfer, alongside `enter`
  to shell in) opens an **in-app SFTP file browser** — not a shell-out to the
  system `sftp` binary.
- Built directly against a Go SFTP library (`pkg/sftp` + `golang.org/x/crypto/
  ssh` for the underlying connection) rather than shelling out, so dial owns
  the whole UI.
- **Core browser UX** (following the same static, high-contrast, low-light-
  friendly list styling as the host picker — no cramped/dense text):
  - Split view or toggle-view of local vs. remote directory listing.
  - Arrow-key navigation through directories on both sides; `enter` on a
    directory descends into it, a clear "up one level" entry at the top of
    each listing.
  - **Batch selection:** a keypress (e.g. `space`) toggles selection on the
    highlighted entry without moving the cursor, same pattern as v1's `f`
    favourite toggle. Multiple files (and/or directories) can be selected
    before triggering a push/pull.
  - Push (local → remote) or pull (remote → local) on the current selection —
    a single file, multiple files, a single directory, or a mix.
  - **Directory transfers are recursive**, walking the full tree on the
    source side and recreating structure on the destination side.
  - Progress UI: per-item progress plus an aggregate "3 of 12 files, 40% of
    total" view when a batch/directory transfer is in flight — legible at the
    same size/contrast as the rest of the UI.
  - Clear error states: connection drop mid-transfer, permission denied, disk
    full, file already exists (prompt overwrite/skip/rename rather than
    silently clobbering) — applies per-item within a batch, with an option to
    apply the same choice to all remaining conflicts rather than prompting
    per file.
- **Resume on failure: basic, same-session only.** If a transfer (or a batch/
  directory transfer) is interrupted — network drop, connection reset — and
  retried within the same `dial` session, dial seeks both the local and
  remote file to the last confirmed byte offset and continues rather than
  restarting from zero. This applies per-file within a batch/directory
  transfer (finished files stay finished; only the interrupted one resumes).
  - **Explicitly not included:** resume that survives closing/restarting
    `dial` or the machine. That requires persisting transfer state to disk and
    verifying partial files weren't corrupted before trusting them — a
    meaningfully bigger, separate piece of work. Flag if this turns out to
    matter in practice (e.g. regularly moving very large files where a lost
    session is costly) and it can be scoped as a v2.x follow-up.
- Reuses the connection details already resolved from `~/.ssh/config` for the
  selected host (same auth, same `ProxyJump` if present) — no separate
  credential entry.
- **Gate coverage:** as already decided in §3.4, the YubiKey gate (when
  enabled) covers entry into this mode too, not just app launch.

### 3.2 How to pick the action (shell vs SFTP) — **decided**
- Second keypress on the highlighted host, not a separate menu: `enter` = ssh,
  `t` = sftp. Keeps the static-list simplicity intact rather than adding a
  submenu/modal.

### 3.3 Distribution/trust (signing, Homebrew, Scoop, CI) — **deferred, not v2**
- This is packaging/ops work, not a feature, and doesn't block daily use on
  your own machines via Syncthing + manual install. Flagging as a separate
  future task rather than bundling into this feature-focused v2 pass.

### 3.4 Gate ergonomics — **small change: gate now also covers SFTP**
- Every-launch touch stays as-is (no idle-timeout added — the report notes
  this as a "worth adding later" maybe, not a v2 must-have).
  **Assumption flagged:** leaving idle-timeout out of v2; say the word if you
  want it added now instead.
- The YubiKey gate (when enabled) now also gates the new `t` (SFTP) action,
  not just app launch, since it's a second way to move data off/onto a host.

### 3.5 Recovery-code UX — **unchanged**
- Current design (terminal-only, shown once, single-use, auto-rotates) stays
  as-is. No changes requested and the report doesn't flag it as a problem.

### 3.6 Richer metadata (tag grouping/colour-coding) — **in scope, small**
- Add optional **section headers by tag** in the static list when sorted
  alphabetically or by tag (new sort mode candidate — see note below).
- Colour-code tag pills using the existing `internal/theme` high-contrast
  palette (e.g. a distinct colour per tag), not free-form colour — keeps the
  low-light-readable requirement intact.
- **Open question for you, not decided here:** should "group by tag" be its
  own 4th+ sort mode, or a toggle independent of sort? Recommend: independent
  toggle (`g` to group), since it's orthogonal to alphabetical/recent/most-used
  ordering. Flagging for Claude Code to confirm with you if ambiguous during
  build.

### 3.7 Search behaviour (fuzzy vs substring) — **kept as substring, not changed**
- Report explicitly chose substring to honour the "readable, not cramped"
  ethos. No reason surfaced in this conversation to revisit — leaving as-is
  for v2.

### 3.8 Multi-select / batch actions across hosts — **deferred, flagged as scope creep**
- Note: this is distinct from the batch *file* selection now included in the
  SFTP browser (§3.1), which is about selecting multiple files/folders within
  one host's transfer session. This item is about running a command or
  transfer across *multiple hosts* at once — the report itself raises this as
  possible scope creep. Recommend leaving it out of v2 — that's a meaningfully
  different tool shape (more like an orchestration tool than a picker) and
  deserves its own dedicated design pass if wanted later, not a bolt-on here.

### 3.9 Connection history/analytics beyond recents — **small addition**
- Add a **last-connected timestamp** shown in the preview pane (already
  tracking recency internally for sort — this just surfaces it visually).
- Per-host notes: **deferred** — no clear use case surfaced yet; easy to add
  later without restructuring anything, so not worth the effort now.

---

## v2 Scope Summary

**Building now:**
1. Alphabetical sort mode
2. `dial keygen` subcommand (cross-platform, ed25519-default, config-append)
3. In-app SFTP file browser (`t` to open, `pkg/sftp`-based — local/remote
   listings, batch file/folder selection, recursive directory transfers,
   same-session resume-on-failure, aggregate progress, overwrite prompts)
4. Tag grouping toggle + colour-coded tag pills
5. YubiKey gate extended to cover the new SFTP browser
6. Last-connected timestamp in preview pane

**Explicitly deferred (not this pass):**
- Resume that survives closing/restarting dial (crash-persistent resume)
- Idle-timeout gate option
- Distribution/signing/CI/package managers
- Multi-select/batch actions *across hosts* (§3.8) — distinct from in-browser
  batch file selection, which is now in scope
- Per-host notes
- Fuzzy search

---

## Notes for Claude Code
- The SFTP browser is a genuinely new subsystem (`internal/sftp` or similar),
  not a reuse of `internal/connect`'s exec-handover pattern — it needs its own
  connection lifecycle via `pkg/sftp`/`golang.org/x/crypto/ssh` since dial
  stays in the foreground driving the UI, rather than handing off the process
  like the SSH connect flow does.
- `dial keygen` is a new top-level subcommand alongside the existing
  `yubikey enable/disable/status/recovery`, `version`, `help` — not part of
  the TUI picker's keybindings.
- Confirm with the user during implementation: tag-grouping as toggle vs sort
  mode (§3.6), if it's not obvious once in the code.
- Given the SFTP browser is now the largest single piece of v2, worth
  sequencing it last (after alphabetical sort, keygen, tag grouping, last-
  connected timestamp) so the smaller wins land first and the browser gets
  full attention/testing time.
