Skip to content

feat: export a search result as .torrent without downloading it - #125

Merged
baairon merged 2 commits into
baairon:mainfrom
chrispyles:feat/export-torrent-from-details
Jul 31, 2026
Merged

feat: export a search result as .torrent without downloading it#125
baairon merged 2 commits into
baairon:mainfrom
chrispyles:feat/export-torrent-from-details

Conversation

@chrispyles

@chrispyles chrispyles commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds an s shortcut in the results details view that fetches a torrent's .torrent metadata and exports it to your download folder without starting a download. If the metadata is already cached from a prior download, it's copied out immediately; otherwise it does a metadata-only fetch (DownloadQueue.fetchAndExportTorrent in src/download/queue.ts) and tears the torrent handle down the instant metadata arrives, before any piece data can hit disk.

  • s in the details view (? sheet + footer) now triggers the export.
  • Footer (footerHints) and help sheet (HELP_GROUPS) both updated, per the keymap system in src/ui/keymap.ts.
  • Store gained fetchAndExportTorrent and a resultFocus field (mirrors the existing downloadFocus/seedFocus pattern) so the footer can tell list mode from detail mode and swap the s hint's label accordingly.
  • makeStore in scripts/render-previews-impl.tsx and the test harness stub updated for the new Store fields.
  • Added coverage in src/download/queue.test.ts for the cached-metadata fast path, the already-active guard, the network fetch + immediate-teardown path, and the error path.

One decision I'd like opinions on

The downloads page already binds s → "Export torrent file" for items you've downloaded. To stay consistent with that, this PR also binds s → export in the results details view — but the results view already uses s for "Sort results" in list mode. Since sort is list-mode-only and export is detail-mode-only, they don't collide at runtime, but the ? help sheet ends up listing s twice under "Search" with two different meanings (sort vs. export), which could read as a typo rather than two context-dependent bindings.

The alternative is e, which is unused in the results view but already means "open folder" on the downloads/seeding pages — so either way s/e ends up meaning something different depending on which screen you're on.

Tradeoff as I see it:

  • s (current choice): consistent verb-to-action mapping across screens ("export" is always s), but duplicates a key within one help-sheet group.
  • e: no duplicate key within a single group, but the same letter now means two different things depending on which screen you're on (export in results, open-folder in downloads/seeding) — which is arguably worse for muscle memory, since the mismatch isn't visible in one place the way the duplicate s row is.

Happy to go either way — wanted to surface it rather than pick silently.

Test plan

  • npm run typecheck — clean
  • npm test — 229/229 runnable tests pass. Note: src/download/queue.test.ts (which includes the new coverage) can't execute in the sandbox this PR was authored in due to a pre-existing, unrelated native-module build issue (node-datachannel) — confirmed via a from-source rebuild attempt that it's a toolchain bug, not something this change caused. Would appreciate a CI run / local npm test confirmation on this file specifically.
  • Manually exercised in a real terminal

Checklist

  • npm run typecheck is clean
  • npm test passes
  • New logic has a test (vitest; mock node built-ins for platform code)
  • If I added a key, I updated both HELP_GROUPS and footerHints in src/ui/keymap.ts
  • If I added a Store field, I updated makeStore in scripts/render-previews-impl.tsx
  • OS-touching code works on Windows, macOS, and Linux
  • One concern, with a Conventional Commits title (feat: / fix: / docs: / chore:)

🤖 Generated with Claude Code

https://claude.ai/code/session_01X8mRpPSwcUzB2p4zJQtnQm

Lets you grab the .torrent file for a torrent you haven't downloaded
yet straight from the details view, fetching just the metadata over
the network (destroying the handle the instant it arrives) or reusing
a cached .torrent if one already exists.

Bound to `s` in the details view to mirror the existing "export" key
on the downloads page, at the cost of overloading `s` with "sort" in
the results list (see PR description for the tradeoff).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X8mRpPSwcUzB2p4zJQtnQm
@chrispyles chrispyles changed the title feat(ui): export a search result as .torrent without downloading it feat: export a search result as .torrent without downloading it Jul 29, 2026
@chrispyles
chrispyles marked this pull request as ready for review July 29, 2026 02:06
@baairon

baairon commented Jul 29, 2026

Copy link
Copy Markdown
Owner

Thanks for this, and for raising the key question instead of picking silently. I was interested in merging it from the start.

On the keys, let's split them: s stays Sort in the results view and e becomes Export. Sorting gets used far more than exporting, so the common action keeps its key and the rarer one moves.

Three things to fix up first:

  • Rebind Export from s to e in the results detail view, and update both HELP_GROUPS and footerHints to match
  • fetchAndExportTorrent never settles when a magnet has no peers, since neither onMetadata nor onError fires. Race it against a 20s timeout that removes the handle and resolves null, which falls into the existing failure notice. No new copy needed
  • Rename the help label to Export as .torrent. The current one is 32 characters, one past the longest existing label, and that single character is what shifts the packing widths in helpLayout.test.ts and drops a 136 column terminal from four columns to three

Push those and I will get CI running on it.

Splits sort and export back apart per review: s stays Sort (used far
more often) and e becomes Export in the results detail view, freeing
up the duplicate s row in the ? sheet. fetchAndExportTorrent now races
the metadata fetch against a 20s timeout so a no-peer magnet resolves
null instead of hanging forever. Shortening the help label back under
the packing threshold restores the four-column layout at 136 cols.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X8mRpPSwcUzB2p4zJQtnQm
@chrispyles

Copy link
Copy Markdown
Contributor Author

@baairon thanks for the review; I've pushed the changes you requested. Please let me know if any more changes are needed.

@baairon
baairon merged commit a701755 into baairon:main Jul 31, 2026
6 checks passed
@baairon

baairon commented Jul 31, 2026

Copy link
Copy Markdown
Owner

merged! thank you

@chrispyles
chrispyles deleted the feat/export-torrent-from-details branch August 1, 2026 17:49
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.

2 participants