nix: resolve the archive's terminfo, follow launcher symlinks, ship a desktop entry #369

Merged
natsukium merged 4 commits from dist/terminfo-and-desktop-entry into main 2026-09-23 14:28:55 +09:00
Owner

What

  1. Archive launchers export TERMINFO_DIRS and follow symlinks (nix/dist-linux.sh, nix/dist-macos.sh). The launchers prepend <root>/share/terminfo and keep any existing value. An unset variable gets a trailing empty element, which stands for the host's default directories. The export is skipped when the directory is already listed, because the CLI launcher execs the client launcher. The launchers now resolve a symlink to themselves with a plain readlink loop, which is POSIX sh and works with bash 3.2 and older macOS. The felis doctor terminfo hint now says tic -x share/terminfo/felis.terminfo applies to a source checkout.
  2. Linux desktop entry. share/applications/felis.desktop is validated with desktop-file-validate in nix/package.nix and installed to $out/share/applications. home-manager picks it up through home.packages. The Linux archive ships the entry and the manifest script checks for it. On Linux the client sets Wayland app_id / X11 WM_CLASS to felis to match StartupWMClass.

Why

The rc.5 audit found two problems. First, a session from the Linux archive on stock ubuntu:24.04 had TERM=xterm-felis but no way to resolve it, so tput colors failed. Second, a bin/felis symlinked into ~/.local/bin exited with 127. There was also no desktop entry and no stable window class.

Principle check: 1 through 4 pass. This is packaging for the launcher and the WM, which are the dedicated tools, and not an in-terminal feature.

How verified

  • nix build .#felis-dist, then linux-archive-manifest.sh on the unpacked tree.
  • linux-archive-smoke.sh in ubuntu:24.04 (rootless docker, same mounts as release.yml): all sections pass. That includes the new b4 check: a session started through a two-hop symlink gets tput colors = 256, the doctor terminfo row is ok, and the child's TERMINFO_DIRS is exactly /opt/felis/share/terminfo:. The GUI marker smoke also passes.
  • desktop-file-validate passes, both standalone and inside the package build.
  • The client under Xvfb reports WM_CLASS = "felis", "felis". Wayland app_id was not observed because no compositor was available, but it is the same winit field.
  • just check passes.
  • The macOS smoke extension (a linked launcher, plus the system /usr/bin/tput) was only syntax- and shellcheck-checked locally. It runs on the macOS runner.
## What 1. **Archive launchers export `TERMINFO_DIRS` and follow symlinks** (`nix/dist-linux.sh`, `nix/dist-macos.sh`). The launchers prepend `<root>/share/terminfo` and keep any existing value. An unset variable gets a trailing empty element, which stands for the host's default directories. The export is skipped when the directory is already listed, because the CLI launcher execs the client launcher. The launchers now resolve a symlink to themselves with a plain `readlink` loop, which is POSIX sh and works with bash 3.2 and older macOS. The `felis doctor` terminfo hint now says `tic -x share/terminfo/felis.terminfo` applies to a source checkout. 2. **Linux desktop entry.** `share/applications/felis.desktop` is validated with `desktop-file-validate` in `nix/package.nix` and installed to `$out/share/applications`. home-manager picks it up through `home.packages`. The Linux archive ships the entry and the manifest script checks for it. On Linux the client sets Wayland `app_id` / X11 `WM_CLASS` to `felis` to match `StartupWMClass`. ## Why The rc.5 audit found two problems. First, a session from the Linux archive on stock ubuntu:24.04 had `TERM=xterm-felis` but no way to resolve it, so `tput colors` failed. Second, a `bin/felis` symlinked into `~/.local/bin` exited with 127. There was also no desktop entry and no stable window class. Principle check: 1 through 4 pass. This is packaging for the launcher and the WM, which are the dedicated tools, and not an in-terminal feature. ## How verified - `nix build .#felis-dist`, then `linux-archive-manifest.sh` on the unpacked tree. - `linux-archive-smoke.sh` in `ubuntu:24.04` (rootless docker, same mounts as `release.yml`): all sections pass. That includes the new b4 check: a session started through a two-hop symlink gets `tput colors` = 256, the doctor terminfo row is ok, and the child's `TERMINFO_DIRS` is exactly `/opt/felis/share/terminfo:`. The GUI marker smoke also passes. - `desktop-file-validate` passes, both standalone and inside the package build. - The client under Xvfb reports `WM_CLASS = "felis", "felis"`. Wayland `app_id` was not observed because no compositor was available, but it is the same winit field. - `just check` passes. - The macOS smoke extension (a linked launcher, plus the system `/usr/bin/tput`) was only syntax- and shellcheck-checked locally. It runs on the macOS runner.
A shell started from the relocated archives gets TERM=xterm-felis, but
nothing told ncurses where the archive's compiled entry lives, so on a
host without the entry (a stock ubuntu:24.04, for one) `tput colors`
failed with an unknown terminal until the user exported TERMINFO_DIRS
by hand. The launchers already export XLOCALEDIR into the environment
that every session inherits; TERMINFO_DIRS rides the same path. An
existing value is kept after the archive's directory, and an unset one
becomes a trailing empty element so the host's default directories stay
searched. The export is skipped when the directory is already listed,
because the CLI's launcher execs the client's and would otherwise
prepend the tree twice.

The launchers also follow symlinks to themselves before locating the
tree: linking bin/felis into ~/.local/bin made `dirname "$0"` point at
the wrong directory and the exec failed with 127. The loop uses plain
`readlink` because `readlink -f` is absent from older macOS, whose
launcher gets the same loop.

With the export in place `felis doctor` finds the entry under the
archive, so its hint no longer needs to steer archive users at
`tic -x share/terminfo/felis.terminfo`, a source file the archives do
not ship; the hint now says it applies to a source checkout.

Both archive smokes now spawn a session through a two-hop symlink to
bin/felis and require `tput colors` to answer 256 (the system tput on
macOS, whose older ncurses is the reader the entry's limits exist for),
and the Linux smoke also requires doctor's terminfo row to pass and the
child's TERMINFO_DIRS to name the tree exactly once.

Assisted-by: Claude Code
nix, client: ship a Linux desktop entry and name the window to match it
Some checks failed
bench / Criterion full-suite snapshot (pull_request) Has been skipped
fuzz / cargo fuzz nightly long-run (pull_request) Has been skipped
bench / Criterion regression gate (pull_request) Has been cancelled
darwin / detect relevant changes (pull_request) Has been cancelled
darwin / build felis (aarch64-darwin) (pull_request) Has been cancelled
darwin / publish felis (aarch64-darwin) (pull_request) Has been cancelled
darwin / darwin docs-only no-op (pull_request) Has been cancelled
fuzz / cargo fuzz smoke (per target) (pull_request) Has been cancelled
pr / nix flake check (pull_request) Has been cancelled
pr / cargo build / clippy / test / deny (pull_request) Has been cancelled
pr / wire schema is compatible with the base (pull_request) Has been cancelled
pr / frontend smoke (x86_64-linux) (pull_request) Has been cancelled
pr / publish felis (x86_64-linux) (pull_request) Has been cancelled
windows / detect relevant changes (pull_request) Has been cancelled
windows / cargo clippy (Windows cross) (pull_request) Has been cancelled
windows / cargo nextest (Windows) (pull_request) Has been cancelled
windows / frontend smoke (Windows) (pull_request) Has been cancelled
windows / package felis (x86_64-pc-windows-msvc) (pull_request) Has been cancelled
windows / windows docs-only no-op (pull_request) Has been cancelled
588520081b
Neither the Nix package nor the Linux archive installed a desktop entry,
so felis did not appear in application launchers, and the client left
winit's defaults for the Wayland app_id and X11 WM_CLASS, which gave a
window manager nothing stable to pair a window with an entry or to
match in a window rule.

share/applications/felis.desktop is the one source: the package
validates it with desktop-file-validate at build time and installs it
under share/applications (the home-manager module picks it up through
home.packages), and the archive carries it in the same place, checked
by the manifest script. It uses the generic utilities-terminal icon
because felis has no icon of its own yet, and Exec=felis because both
installs put a `felis` on PATH; install.md says how an archive user
copies the entry and when to point Exec at the absolute path.

The client sets app_id and WM_CLASS to "felis" on Linux, the name
StartupWMClass declares. winit keeps both in one field, so the Wayland
extension trait's setter covers the X11 backend as well.

This is packaging for launchers and window managers, the dedicated
tools principle 1 hands those jobs to, not a new in-terminal feature.

Assisted-by: Claude Code
nix: resolve the Linux launcher's root physically
Some checks failed
bench / Criterion full-suite snapshot (pull_request) Has been skipped
fuzz / cargo fuzz nightly long-run (pull_request) Has been skipped
bench / Criterion regression gate (pull_request) Failing after 2m4s
darwin / detect relevant changes (pull_request) Successful in 3s
darwin / darwin docs-only no-op (pull_request) Has been skipped
darwin / build felis (aarch64-darwin) (pull_request) Successful in 54s
darwin / publish felis (aarch64-darwin) (pull_request) Has been skipped
fuzz / cargo fuzz smoke (per target) (pull_request) Successful in 1m5s
pr / nix flake check (pull_request) Successful in 31s
pr / cargo build / clippy / test / deny (pull_request) Successful in 1m40s
pr / wire schema is compatible with the base (pull_request) Successful in 11s
pr / frontend smoke (x86_64-linux) (pull_request) Successful in 1m16s
pr / publish felis (x86_64-linux) (pull_request) Has been skipped
windows / detect relevant changes (pull_request) Successful in 3s
windows / windows docs-only no-op (pull_request) Has been skipped
windows / cargo nextest (Windows) (pull_request) Successful in 4m8s
windows / cargo clippy (Windows cross) (pull_request) Successful in 11s
windows / frontend smoke (Windows) (pull_request) Successful in 1m45s
windows / package felis (x86_64-pc-windows-msvc) (pull_request) Has been skipped
8b2f58bc1c
The CLI launcher exports its terminfo path and a sibling launcher, reached through /proc/self/exe, compares against its own. With a plain pwd the first keeps a symlinked directory name while the second sees the physical path, so the dedupe misses and TERMINFO_DIRS grows a second copy of the same tree. pwd -P gives both the same spelling, as the macOS launcher already does.

Assisted-by: Claude Code
Merge remote-tracking branch 'origin/main' into dist/terminfo-and-desktop-entry
Some checks failed
bench / Criterion full-suite snapshot (pull_request) Has been skipped
fuzz / cargo fuzz nightly long-run (pull_request) Has been skipped
bench / Criterion regression gate (pull_request) Failing after 2m5s
darwin / detect relevant changes (pull_request) Successful in 3s
darwin / darwin docs-only no-op (pull_request) Has been skipped
darwin / build felis (aarch64-darwin) (pull_request) Successful in 53s
darwin / publish felis (aarch64-darwin) (pull_request) Has been skipped
fuzz / cargo fuzz smoke (per target) (pull_request) Successful in 1m3s
pr / nix flake check (pull_request) Successful in 32s
pr / cargo build / clippy / test / deny (pull_request) Successful in 1m40s
pr / frontend smoke (x86_64-linux) (pull_request) Successful in 1m21s
windows / windows docs-only no-op (pull_request) Has been skipped
windows / cargo nextest (Windows) (pull_request) Successful in 4m8s
windows / package felis (x86_64-pc-windows-msvc) (pull_request) Has been skipped
pr / wire schema is compatible with the base (pull_request) Successful in 11s
pr / publish felis (x86_64-linux) (pull_request) Has been skipped
windows / detect relevant changes (pull_request) Successful in 3s
windows / cargo clippy (Windows cross) (pull_request) Successful in 11s
windows / frontend smoke (Windows) (pull_request) Successful in 1m50s
0f4608d8b7
# Conflicts:
#	nix/package.nix
natsukium deleted branch dist/terminfo-and-desktop-entry 2026-09-23 14:28:55 +09:00
Commenting is not possible because the repository is archived.
No description provided.