diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bfb87f8..d5b0eef 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -66,12 +66,14 @@ jobs: env: DIST_TAR: podtui-${{ matrix.plat }}-${{ matrix.arch }}.tar.gz run: | - # The embedded runtime reads the launching process's CWD bunfig.toml. - # This repo's bunfig lists a preload the standalone can't resolve - # ("preload not found"), so kicking the binary from the workspace root - # would falsely fail every build. cd into a clean dir first. + # The binary is compiled with bunfig autoload disabled + # (autoloadBunfig: false in build.ts), so it must boot even from a + # directory holding a bunfig.toml with a top-level preload the + # standalone can't resolve. Plant one to make this a real regression + # test for "preload not found". SMOKE_DIR=$(mktemp -d) tar -xzf "dist/$DIST_TAR" -C "$SMOKE_DIR" + printf 'preload = ["./definitely-missing.ts"]\n' > "$SMOKE_DIR/bunfig.toml" cd "$SMOKE_DIR" ./podtui-*/podtui --version diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b2d5f3b..5d5e2f4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -38,6 +38,7 @@ The app is a TUI — it expects a real terminal (Ghostty, kitty, iTerm2, | `bun run lint` | Type-check | | `bun run build` | Bundle JS into `dist/` + copy native libs (the `podtui` npm script path) | | `make dist` | Compile the standalone binary + make the current platform's tarball | +| `make dist-mac` / `make dist-linux` | Aliases for `dist` on their platform (CI runs these) | | `make clean` | Remove `dist/` | ## Repository layout @@ -90,19 +91,21 @@ Cavacore smoke test: `bun tests/cavacore-smoke.ts` ## Gotchas (read before touching anything) -1. **Never add a top-level `preload` to `bunfig.toml`.** - A compiled PodTui binary's embedded runtime reads the *launching process's* - CWD `bunfig.toml`, and a `preload` entry points at a module the standalone - can't resolve (`@opentui/solid/preload`) → the binary dies at startup with - `preload not found`. This is why `bunfig.toml` has **no** top-level - `preload`; dev-mode preloading happens via explicit `--preload` flags in - `package.json`. The `[test]` section *does* keep a preload — that only - affects `bun test`. +1. **The compiled binary must keep bunfig autoload disabled.** + `build.ts` compiles the standalone with `autoloadBunfig: false`, so its + embedded runtime *never* reads the launching CWD's `bunfig.toml`. Without + that flag, a top-level `preload` in the CWD bunfig (common in Bun project + dirs) resolves against the CWD rather than the binary and kills startup + with `preload not found`. Don't remove the flag. Preloads for dev/test + belong in the explicit `--preload` flags in `package.json` and the + `[test]` section of `bunfig.toml` — not as a top-level entry. -2. **Smoke-test the compiled binary from a bunfig-free dir.** - Because of (1), `./dist/podtui --version` run from the repo root launched - inside CI would fail. CI always unpacks the tarball into a `mktemp` dir - before booting. Do the same when testing a release build locally. +2. **Smoke-test the binary from a dir with a poisoned bunfig.** + The CI smoke test unpacks the tarball into a `mktemp` dir, drops a + `bunfig.toml` containing an unresolvable top-level `preload` next to it, + and boots the binary — proving bunfig autoload stayed disabled. `./dist/ + podtui --version` must work from any directory, including the repo root; + do the same check when testing a release build locally. 3. **Homebrew's dylib-repair warning is benign.** `brew install` may print “load commands do not fit in the header … needs @@ -166,7 +169,7 @@ Releases are built and published from **tags** test: `brew install mikefreno/tap/podtui`. 5. **AUR packaging** (`packaging/aur/PKGBUILD`): the `podtui-bin` package is staged, not yet published (AUR account registrations are closed; see the - README note in section 3). On each release, keep the AUR sources in sync + README's Installation section). On each release, keep the AUR sources in sync with the new tag: bump `pkgver`, recompute the two tarball `sha256sums` entries, keep the `LICENSE` asset source (the workflow above uploads `LICENSE` to every release), and regenerate `packaging/aur/.SRCINFO` with @@ -190,13 +193,36 @@ make dist # builds the binary + tarball for THIS machine only Bun cannot cross-compile — the other platforms come from CI. +## Distribution & packaging + +A release tarball is three files sitting side by side: the `podtui` binary +plus its two FFI libraries (`libopentui.`, +`libcavacore.`). The sibling rule above is why they ship together. + +PodTui deliberately ships **no** `.deb`, `.rpm`, Flatpak, or Snap packages: +for a terminal app that's overwhelmingly installed through repositories or +archives, those formats add desktop-sandboxing overhead and a packaging tax +with little benefit. Instead: + +- **GitHub Release tarballs** are the universal path — one upload per + OS/arch, works on any distro with `curl` + `tar`. +- **AUR (`podtui-bin`)** covers Arch/Manjaro with the same binary through the + native package manager. +- **Nix / cross-distro** users build from source (or a Nix flake can be added + later). + +This keeps maintenance to a single build per OS/arch while still reaching the +vast majority of desktop Linux users. The AUR PKGBUILD lives in +`packaging/aur/` and can be built locally to test before publication: + +```bash +cd packaging/aur && makepkg -si +``` + --- ## Open items / things to sort out -- **LICENSE**: `README.md` says "TBD — choose and document a license before - the first release". Pick one (MIT/BSD-3) and add `LICENSE` + update the - README footer. - **Native libs in `dist/` still need committing?** No — they're built from sources kept in the repo (`cava/`, `node_modules/@opentui/core-*`). Only `src/native/libcavacore.dylib` is a committed binary artifact; macOS arm64 diff --git a/Makefile b/Makefile index 0de4a07..751f709 100644 --- a/Makefile +++ b/Makefile @@ -47,9 +47,8 @@ native: scripts/build-cavacore.sh ## Standalone binary + native-libs tarball for the current platform. -## Unaffected by bunfig.toml at build time. Note: the compiled runtime reads -## the launching process's CWD bunfig.toml, so smoke tests must run the binary -## from a bunfig-free dir (see release.yml). +## Built with bunfig autoload disabled (build.ts sets autoloadBunfig: false), +## so the embedded runtime ignores any bunfig.toml in the launching directory. dist: bun run build.ts --compile diff --git a/README.md b/README.md index 7a6e471..bf7d148 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ # PodTui -A keyboard-first, yazi-style terminal podcast client written in TypeScript and -built on [OpenTUI](https://github.com/opentui/opentui). Subscribe to RSS feeds, +A keyboard-first, terminal podcast client built on [OpenTUI](https://github.com/opentui/opentui). Subscribe to RSS feeds, browse episodes in a three-pane file-manager layout, and play audio through an external player with full transport control — all from your terminal. @@ -11,8 +10,7 @@ external player with full transport control — all from your terminal. `Enter` to open, `1–6` / `[` `]` to switch tabs. The tab list is the app root: at launch it fills the current pane, and drilling into a tab's contents slides it into the parent pane. -- **Three-pane view** — parent / current / preview (Up | Current | Preview), - mirroring yazi's pane model. +- **Three-pane view** — parent / current / preview (Up | Current | Preview). - **Podcast feeds** — add feeds, browse episodes, and manage your library (My Shows, Discover, Feed tabs). - **Search** across your subscribed shows. @@ -22,13 +20,28 @@ external player with full transport control — all from your terminal. - Ships as a **standalone compiled binary** — no runtime or install step beyond a system audio player. +## Quick start + +1. Install PodTui ([Installation](#installation)) and make sure **mpv** is in + your `PATH`. +2. Run `podtui` in your terminal. +3. Press `3` to open **Discover** (or `4` to open **Search**, then `s`), drill + in with `Enter`, and press `Enter` on a show to subscribe. +4. Press `1` (**Feed**) or `2` (**My Shows**), open an episode with `Enter`, + and use `P` to play/pause, `N`/`B` for next/previous, and `shift-.` / + `shift-,` to seek. + +Press `~` any time for in-app help. All keys are remappable — see +[Keybindings](#keybindings). + ## Requirements - A terminal with UTF-8 and modern color support (kitty, iTerm2, WezTerm, - tmux, GNOME Terminal, etc.). + Ghostty, tmux etc.). - **mpv** on `PATH` for audio playback. PodTui drives mpv over JSON IPC, so seek, speed, and position tracking all work. Without `mpv` on `PATH`, - playback is a silent no-op (the `none` backend). + playback is a silent no-op (the `none` backend) — see + [Troubleshooting](#troubleshooting). ## Installation @@ -41,10 +54,6 @@ Linux (arm64/x64). Pick whichever fits your platform. brew install mikefreno/tap/podtui ``` -> The formula installs the standalone binary plus its two native libraries -> side by side (see [Packaging model](#packaging-model)). It does **not** -> depend on Bun. - ### 2. Standalone tarball (all platforms) Grab `podtui--.tar.gz` from the latest @@ -62,68 +71,29 @@ sudo ln -sf /opt/podtui/podtui /usr/local/bin/podtui > The tarball contains `podtui` plus `libopentui.` and > `libcavacore.` **beside it** — keep them together (don't move just the > binary alone), or the native FFI libraries won't load. -> -> One caveat: the embedded runtime reads a `bunfig.toml` from the directory -> you launch from. If that file has a `preload` entry (as Bun project -> directories often do), startup fails with `preload not found`. Launching -> from a normal directory (home, `~/bin`, …) works fine. ### 3. Arch Linux (AUR) ```bash -# Status: PKGBUILD ready, not yet on the AUR (see note below) yay -S podtui-bin # once published ``` -Requires an AUR helper ([paru](https://github.com/morgan/paru)). The AUR -package (PKGBUILD lives in `packaging/aur/`) installs the released binary and -its two FFI sibling libraries into `/usr/lib/podtui/` with a `/usr/bin/podtui` -symlink, and pulls in `mpv` (the sole audio backend) as a dependency. +Requires an AUR helper ([paru](https://github.com/morgan/paru)); the package +pulls in `mpv` as a dependency. -> **Not yet on the AUR.** The `podtui-bin` PKGBUILD and `.SRCINFO` are ready -> in `packaging/aur/` and can be built locally today: -> -> ```bash -> cd packaging/aur && makepkg -si -> ``` -> -> Publishing is on hold until [AUR account registrations](https://aur.archlinux.org) -> reopen (suspended while the AUR team works on suspicious-package -> moderation). +> **Not yet on the AUR.** The `podtui-bin` package is staged and awaiting +> publication (AUR account registrations are currently suspended). Until it +> lands, use the standalone tarball above. ### 4. From source -Requires [Bun](https://bun.sh) ≥ 1.2. - -```bash -git clone https://github.com/mikefreno/podtui.git -cd podtui -bun install -bun run build:native # build the cavacore FFI lib from C source -bun run dev # run with hot reload, or: bun start -``` - -## Linux distribution notes - -PodTUI deliberately does **not** ship `.deb`, `.rpm`, Flatpak, or Snap -packages. For a terminal application that's overwhelmingly installed through -repositories or archives, those formats add desktop-sandboxing overhead and a -packaging tax with little benefit. Instead: - -- **GitHub Release tarballs** are the universal path — one upload, works on - any distro with `curl` + `tar`. -- **AUR (`podtui-bin`)** covers Arch. Anyone on Arch/Manjaro gets the same - binary through their native package manager. -- **Nix / cross-distro** users can build from source (or a Nix flake can be - added later). - -This keeps maintenance to a single build per OS/arch and still reaches the -vast majority of desktop Linux users through their preferred path. +PodTui is written in TypeScript and runs on [Bun](https://bun.sh). To build +from source (development, distro packaging, unreleased versions), see +[CONTRIBUTING.md](CONTRIBUTING.md). ## Usage -Launch `podtui` (or `bun src/index.tsx` from the source tree). Press `~` -for the in-app help. +Launch `podtui`. Press `~` for the in-app help. ### Command-line flags @@ -135,30 +105,70 @@ for the in-app help. ### Keybindings -All keys are remappable — edit `~/.config/podtui/keybinds.jsonc`. +All keys are remappable — edit `keybinds.jsonc` in your config directory +(see [Configuration](#configuration)). + +**Movement** | Keys | Action | |------|--------| -| `j` / `k` | Move cursor down / up | -| `J` / `K` | Jump 5 lines | +| `j` / `k` (or `down` / `up`) | Move down / up | +| `J` / `K` | Jump 5 lines down / up | | `ctrl-d` / `ctrl-u` | Page down / up | +| `ctrl-f` / `ctrl-b` | Full page down / up | | `gg` / `G` | Go to top / bottom | -| `h` / `l` | Swipe to parent pane / preview pane | + +**Panes** + +| Keys | Action | +|------|--------| +| `h` / `l` (or `left` / `right`) | Focus parent pane / preview pane | | `Enter` | Open the item under the cursor (a tab, episode, show…) | -| `Space` | Select / toggle selection | +| `shift-enter` | Open with the interactive variant | + +**Selection** + +| Keys | Action | +|------|--------| +| `Space` | Toggle selection | | `v` | Visual mode (multi-select) | +| `ctrl-a` | Select / deselect all | +| `ctrl-r` | Invert selection | +| `Esc` | Cancel / escape | + +**Tabs** + +| Keys | Action | +|------|--------| | `1`–`6` | Jump to tab 1–6 (Feed, My Shows, Discover, Search, Player, Settings) | | `[` / `]` | Previous / next tab | -| `P` (shift) | Play / pause | + +**Commands, help, quit** + +| Keys | Action | +|------|--------| +| `:` or `q` | Open the command palette (type `q` + `Enter` there to quit) | +| `Q` or `ctrl-c` | Quit | +| `~` or `f1` | In-app help | + +**Lists** + +| Keys | Action | +|------|--------| +| `s` | Search | +| `f` | Filter | +| `,` | Sort | +| `.` | Toggle hidden | +| `r` | Refresh | +| `x` | Unsubscribe the focused show (My Shows) | + +**Audio** + +| Keys | Action | +|------|--------| +| `P` | Play / pause | | `N` / `B` | Next / previous episode | | `shift-.` / `shift-,` | Seek forward / backward | -| `s` | Search (in a list) | -| `f` | Filter | -| `r` | Refresh | -| `:` | Command bar | -| `~`, `f1` | Help | -| `q`, `ctrl-c` | Quit | -| `Esc` | Escape / cancel | ## Configuration @@ -175,50 +185,32 @@ default (`$XDG_CONFIG_HOME/podtui` if set). Legacy `feeds.json`, `sources.json`, and `app-state.json` are auto-migrated into `config.json` on first run. -Env overrides: `PODTUI_AUDIO_BACKEND`, `XDG_CONFIG_HOME`. Startup also reads -the same OpenTUI environment variables. +Env overrides: `PODTUI_AUDIO_BACKEND`, `XDG_CONFIG_HOME`. -## Development +## Troubleshooting -```bash -bun install # install dependencies -bun run dev # run with hot reload -bun test # run the test suite -bun run build # bundle JS + copy native libs into dist/ -make native # rebuild cavacore from C source -make lint # type-check (tsc) -``` +**`preload not found` at startup** — this used to happen when the binary was +launched from a Bun project directory whose `bunfig.toml` had a `preload` +entry. Releases are compiled with bunfig autoload disabled +(`autoloadBunfig: false`), so current binaries ignore the CWD's `bunfig.toml` +entirely. If you still hit it, you're on an old release — upgrade. -### Releasing +**No audio — playback is a silent no-op** — PodTui needs **mpv** on your +`PATH`. Install it (`brew install mpv`, `pacman -S mpv`, …) and relaunch. -Tag a release (e.g. `v0.1.0`); CI builds and uploads the per-platform tarballs -to your GitHub Release automatically: +**Homebrew prints a dylib warning** — “load commands do not fit in the header +… needs `-headerpad`” is benign: the app loads its libraries by path, the +install completes, and the app boots normally. -```bash -make dist # build the standalone binary + tarball for THIS platform -make dist-mac # (run on macOS) → podtui-darwin-.tar.gz -make dist-linux # (run on Linux) → podtui-linux-.tar.gz -``` +**The app won't start / no spectrum after moving files** — `podtui` loads its +two native libraries relative to the binary, so keep `podtui`, +`libopentui.*`, and `libcavacore.*` together in the same directory (the +tarball unpacks them side by side). -`make dist` emits a config-independent binary: Bun does not bake bunfig -settings into `--compile` output, and the solid JSX transform is registered in -`build.ts` itself. The binary then embeds the `preload`-free runtime, so launch -it from any normal directory. +## Building from source / contributing -## Packaging model - -A release tarball is three files sitting side by side: - -``` -podtui # standalone compiled binary (embeds the Bun runtime) -libopentui. # OpenTUI native renderer FFI library -libcavacore. # cavacore spectrum FFI library (built from C) -``` - -PodTui loads its native libraries relative to the binary, so **keep them in -the same directory**. The compiled binary embeds the Bun runtime, so it runs -with no Bun installed. Each release builds one tarball per OS/arch in CI; there -is no cross-compilation. +Development setup, the test suite, packaging, and the release process are +documented in [CONTRIBUTING.md](CONTRIBUTING.md). ## License diff --git a/build.ts b/build.ts index e195c07..b851b08 100644 --- a/build.ts +++ b/build.ts @@ -82,6 +82,12 @@ if (COMPILE) { plugins: [solidPlugin], compile: { outfile, + // Don't let the embedded runtime autoload the launching CWD's + // bunfig.toml. A top-level `preload` there (common in Bun project + // dirs) resolves against the CWD, not the binary, so startup dies + // with "preload not found". With autoload disabled, the binary is + // config-independent and boots from any directory. + autoloadBunfig: false, }, }); console.log(`Compiled standalone binary: ${outfile}`); diff --git a/bunfig.toml b/bunfig.toml index 96b1f08..dec780c 100644 --- a/bunfig.toml +++ b/bunfig.toml @@ -1,9 +1,8 @@ -# NO top-level `preload` here — intentional. A compiled PodTUI binary's -# embedded Bun runtime reads the launching process's CWD bunfig.toml, and a -# top-level `preload` entry (e.g. "@opentui/solid/preload", which the -# standalone cannot resolve) makes the binary die at startup with -# "preload not found". Dev/test still get the solid transform via explicit -# `--preload` flags in package.json and the [test] section below. +# No top-level `preload` here — dev/test get the solid JSX transform via the +# explicit `--preload` flags in package.json and the [test] section below. +# Releases don't read this file at all: build.ts compiles the standalone with +# `autoloadBunfig: false`, so its embedded runtime ignores any bunfig.toml in +# the launching directory — no more "preload not found" from CWD bunfigs. [test] preload = "@opentui/solid/preload"