Skip to content

Name the macOS app NVIDIA PAIR in Finder, the Dock, and the menu bar - #147

Merged
Noah-Tervalon-Nvidia merged 1 commit into
developfrom
fix/macos-app-name
Oct 8, 2026
Merged

Noah-Tervalon-Nvidia merged 1 commit into
developfrom
fix/macos-app-name

Conversation

@Noah-Tervalon-Nvidia

Copy link
Copy Markdown
Collaborator

Changelog title

The desktop app is called NVIDIA PAIR

Changelog body

  • The application now appears as NVIDIA PAIR in Finder, the Dock, and the macOS menu bar, instead of the abbreviated PAIR.

Bumps

  • services: patch
  • nvpair-cluster-manager: none
  • nvpair-engine-manager: none
  • nvpair-errors: none
  • nvpair-job-scheduler: none
  • nvpair-manual-nodes: none
  • nvpair-node-info: none
  • nvpair-node-scanner: none
  • nvpair-node-settings: none
  • nvpair-proxy: none
  • nvpair-tui: none
  • nvpair-ui-broker: none
  • nvpair-workload-manager: none

Summary

The macOS bundle shipped as PAIR.app with CFBundleExecutable of PAIR, so
Finder, the Dock, and the menu bar all showed the abbreviation rather than the
product name.

Setting productName to NVIDIA PAIR renames the bundle, which means the
executable inside it and every path that names it move too. This change updates
the three places that hardcoded the old name:

  • desktop/electron-builder.config.ts — the product name and an explicit
    mac.executableName, so the binary inside the bundle keeps a stable name even
    though the bundle directory now contains a space.
  • desktop/scripts/build/macos/uninstall.sh — removes the renamed bundle, and
    the previous one, so an uninstall after an in-place upgrade does not leave the
    old app behind.
  • scripts/wipe-app-data.sh and README.md — the paths and prose that name the
    app.

The space in the bundle name is the part most likely to break something
downstream. Signing resolves the executable through CFBundleExecutable rather
than assuming it matches the bundle, which is handled in the build-and-sign
repository.

No service binary changes, so every component bump is none.

Test plan

  • npm run typecheck, npm run lint, npm run dead-code:check,
    npm run test:unit, npm run service-contracts:check from desktop/.
  • Built and installed a signed macOS arm64 DMG; confirmed the app shows as
    NVIDIA PAIR in Finder, the Dock, and the menu bar, and that the bundled
    nvpair launcher still resolves.
  • Ran desktop/scripts/build/macos/uninstall.sh against an installed build and
    confirmed both the current and previously-named bundles are removed.

The macOS app showed as PAIR while Windows and Linux showed NVIDIA PAIR.
macOS ignores CFBundleDisplayName unless it is localized, and takes the
name from the bundle and CFBundleName, both of which were PAIR.
electron-builder names the bundle after mac.executableName, so the
executable is renamed with it: the app is now NVIDIA PAIR.app, running
NVIDIA PAIR. Linux keeps /opt/PAIR, which its nvpair wrapper embeds.

An install updated in place keeps the PAIR.app name, so the uninstaller
now finds the bundle it ships in and reads its executable name from the
bundle rather than assuming either. The README's uninstall path names
both.

Signed-off-by: Terve <ntervalon@nvidia.com>

@kjlubick kjlubick left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@Noah-Tervalon-Nvidia
Noah-Tervalon-Nvidia merged commit a46e572 into develop Oct 8, 2026
18 of 19 checks passed
pair-release-intent Bot added a commit that referenced this pull request Oct 8, 2026
Apply release intent from PR #147.

Applies-PR: #147
@Noah-Tervalon-Nvidia
Noah-Tervalon-Nvidia deleted the fix/macos-app-name branch October 8, 2026 17:05
Noah-Tervalon-Nvidia added a commit that referenced this pull request Oct 8, 2026
## Stack

Targets `sherief/llamacpp-downloads` (#148), which targets
`fix/macos-app-name` (#147). Merge those first; this diff is only what
sits on top of them.

Rebasing onto the llama.cpp branch added fourteen commits to the
sixty-nine here:

- **Return the terminal interface to develop's** — removes #148's
additions to the old interface, which the rebuild deletes or rewrites,
so each rebuild commit lands on the tree it was written against. The
broker still fronts every engine by default.
- **llama.cpp in the rebuilt interface and in `engine:catalog`** (nine
commits) — the engine manager serves llama.cpp's Hugging Face catalogue
and its search, the desktop relays the query, and the terminal interface
gains llama.cpp's model actions and an upstream search.
- **Four fixes from review** — a declared pipeline decides whether a
model can chat; GGUF draft heads llama.cpp will not pull are skipped; an
over-long search is refused rather than shortened; the model browser no
longer downloads a row it has stopped showing.

## Description

Rebuilds `nvpair-tui` around a node-first tab layout and closes the gaps
against the desktop application, then moves the model catalogue into the
backend so both front ends browse one implementation.

A node is the unit an operator reasons about, so the tabs become Nodes,
Jobs, Service, Errors, and Logs, and everything specific to one machine
hangs off its row. The ten tabs this replaces spread one machine's facts
across four of them: its engines in one, its models in another, its
ports in a third, its cluster standing in a fourth.

Much of the rest is about claiming no more than the backend guarantees.
A port change reports the port actually bound rather than the one
requested. Clearing an error is only offered where clearing sticks. The
cluster name is labelled as this machine's own. Node presence follows
the broker's snapshot instead of a timestamp that never advanced.

## How to review this

Seventeen commits, in dependency order. Four are prerequisites, one is
the rewrite, and the rest are separable features and fixes. **The
rewrite (`5381e2f1`) is 51 files and is best read in the order below** —
it follows the data rather than the alphabet.

The five views only exist together: the shell sizes them, they share its
status line and frame budget, and the tab set *is* the change. Splitting
them further would have meant authoring intermediate versions of
`nodedetail.go` and `jobs.go` that never existed, so the guided read is
offered instead.

### Read the commits in this order

1. `6b377e78` frame cap single-sourced across four hops — 5 files
2. `e99efb05` `engine:catalog` in the engine manager — the `+109k` is
the committed Ollama list; review `catalog.go` and `catalog_test.go`
3. `001b0e4a` desktop served from that catalogue — the `−109k` is the
same list leaving Electron
4. `c414dbf0` one `inference-dispatcher`, from `cli-bin`
5. `9a61f3de` broker stream errors reported as disconnects
6. `e70fbd21` shared frame primitives (table, status line, row budget)
7. `5381e2f1` **the rewrite** — see the file order below
8. `ec7a6c84` release version stamped for the update notice
9. `144e472f` engine launch-arguments editor, ports onto the same write
10. `32738dcd` documentation and the architecture rule
11. `953a9ab0` CI's staged-binary check learns about the demo client
12. `eaedf559` light-terminal readability
13. `7f5610da` the `requestId` a settings commit requires
14. `67ca9630` background detected before Bubble Tea takes stdin
15. `fbb37e0a` local settings snapshots kept current
16. `94d041d2` leaving the Nodes tab returns to the list
17. `33b5470c` settings verdict taken from the backend

Commits 12–17 came from running the build on real hardware. Four of them
fix defects in commit 9; each says what the backend actually does, which
is worth reading even where the diff is small.

### Read the rewrite's files in this order

**Start with the shape** — 400 lines, and the rest follows from it:

1. `ui/ui.go` — the tab set. The whole change in 27 lines
2. `ui/model.go` — the shell: frame budget, tab switching, the notice
row
3. `ui/keys.go` — why the bindings are what they are
4. `ui/view.go` *(unchanged)* — the contract the five views implement

**The data, before the screens that render it:**

5. `ui/nodeswire.go` — the discovery, cluster, and manual wire shapes
6. `ui/nodesmodel.go` — merging those three feeds onto one key. The
heart of the Nodes tab
7. `ui/nodenames.go` — resolving a node id to a name
8. `ui/engineswire.go` — engine status and model inventory shapes

**The tabs, simplest first:**

9. `ui/logs.go`, `ui/errors.go` — smallest, and they establish the view
idioms
10. `ui/service.go` — worker liveness, log level, data reset
11. `ui/proxystatus.go` — per-engine facade readiness and ports
12. `ui/jobs.go` — the endpoints and live inference work
13. `ui/nodes.go` — the merged node table, filtering, pairing keys
14. `ui/invite.go` — pairing events scoped to their session

**The drill-down and its two satellites** — the largest file, read last:

15. `ui/nodedetail.go` — engines and models for one machine
16. `ui/nodetelemetry.go` — the direct `/v1/node-info` poll
17. `ui/catalog.go` — the downloadable-model browser
18. `ui/demo.go`, `ui/demoschedule.go` — the Inference Demo

**Then the tests**, which read as the specification:

19. `ui/framebudget_test.go` — no view overflows its frame at any
supported size
20. `ui/table_test.go` — every table has columns at construction
21. `ui/nodesmodel_test.go`, `ui/nodedetail_test.go`,
`ui/service_test.go` — the per-area behaviour
22. the remaining `_test.go` files

**Deletions need no reading**: `cluster.go`, `engines.go`, `health.go`,
`manualnodes.go`, `proxies.go`, `settings.go`, `workloads.go` are the
tabs the five replace.

## Scope

Included: the terminal interface rewrite, its Inference Demo and update
notice; `engine:catalog` replacing the Electron-only model hub; one
`inference-dispatcher` in `cli-bin`; an editor for an engine's launch
arguments with the two port fields moved onto the same revision-checked
write; documentation.

Excluded: no change to routing, scheduling, or proxy behaviour; none to
pairing or trust; the desktop renderer's settings UI is untouched, only
its catalogue source moved.

## Validation

Everything CI runs, run locally on macOS arm64, Go 1.26.3, Node 22:

- `node scripts/spdx-headers.mjs` — 1039 checked, 0 missing
- `npm --prefix desktop run verify:build-scripts`,
`service-contracts:check`, `typecheck`, `lint`, `dead-code:check`,
`test:unit` (230 passing)
- `npm --prefix desktop run build:modular-binaries -- --force` — 13
binaries
- Go component tests with `-race` across `shared`, `nvpair-tui`,
`nvpair-ui-broker`, `nvpair-engine-manager`
- `cd services/tests && go test ./... -count=1 -timeout=20m` — passes,
no failures
- `services/build.sh`, then the staged-binary comparison this branch
updates

Two failures are pre-existing and reproduce identically on `develop` in
a clean worktree: `TestUninstallTerminatesRunningInstance` in
`nvpair-engine-manager`, and
`services/shared/splitlisten/splitlisten_test.go` is not `gofmt`-clean.

Manual, on a Mac and a DGX Spark over SSH: pairing, engine lifecycle,
model download, the demo, and the settings editor. The
`ui.ReleaseVersion` stamp was read back out of both binary sets, since a
`-X` against a wrong symbol path fails silently. Terminal background
detection was checked in a pty against a terminal that answers the query
and one that ignores it.

## Risk

The engine settings path is the part to look at. The two port fields
previously wrote through `engine:set-port` and the proxy's own
`set-port`, which meant two writers with no shared revision — the last
to finish won and neither could tell it had lost. All three fields are
now one revision-checked write through `engine:preview-settings` and
`engine:apply-settings`. That also makes them editable on a peer, which
the old path refused because it had no remote form; whether a given
engine accepts the write is the snapshot's `Editable` answer rather than
a rule kept in the front end.

`jsonrpc.WorkerFrameBytes` raises the inbound frame cap on three hops
that carry large worker replies, from the 1 MiB default to 8 MiB. The
Ollama catalogue is roughly 1.9 MiB and could not previously cross them;
an over-long frame is a terminal read error rather than a dropped
message, so the hops have to agree.

No wire-format removals, no persisted-data migrations, no change to how
nodes authenticate to each other.

## Release intent

<!-- pair-release-intent:v1 -->
### Changelog title
A rebuilt terminal interface, and one model catalogue for both front
ends

### Changelog body
- The terminal interface is organised around machines: Nodes, Jobs,
Service, Errors, and Logs, with a per-machine drill-down for engines,
models, ports, and hardware.
- Engines can be started with your own arguments and environment
variables, edited from the terminal interface on this machine or on a
paired one. Changes are validated before they are saved.
- Downloadable models are served by the engine manager, so the terminal
interface and the desktop application browse the same catalogue.
- The Inference Demo runs from the terminal interface's Jobs tab, so a
headless machine can demonstrate routing.
- The terminal interface says when a newer release of PAIR is published,
on every tab until dismissed. It installs nothing.
- Pairing keys follow the words on screen: `p` pairs, `a` accepts, `f`
finds a machine by address.

### Bumps
- services: major
- nvpair-cluster-manager: none
- nvpair-engine-manager: minor
- nvpair-errors: none
- nvpair-job-scheduler: none
- nvpair-manual-nodes: none
- nvpair-node-info: none
- nvpair-node-scanner: none
- nvpair-node-settings: none
- nvpair-proxy: none
- nvpair-tui: major
- nvpair-ui-broker: patch
- nvpair-workload-manager: none
<!-- /pair-release-intent:v1 -->

## Checklist

- [x] I have read the [Contributing
Guidelines](https://github.com/NVIDIA/Personal-AI-Router/blob/main/CONTRIBUTING.md).
- [x] Every commit is signed off (`git commit -s`), certifying the
[Developer Certificate of Origin](https://developercertificate.org/).
- [x] New or existing tests cover the change.
- [x] Relevant documentation is updated.
- [x] I checked the diff, changed filenames, and commit messages for
credentials, private data, internal URLs, internal issue identifiers,
and generated artifacts.
- [x] I recorded the validation commands and results above.
- [x] I declared version bumps in the release-intent block above.
`services/versions.json` is written by automation — do not edit it by
hand.

---------

Signed-off-by: Terve <ntervalon@nvidia.com>
kjlubick pushed a commit that referenced this pull request Oct 8, 2026
<!-- pair-release-intent:v1 -->
### Changelog title
Safer engine removal and data resets

### Changelog body
- Removing an engine never follows a symlink or Windows junction into
another folder.
- An LM Studio models folder linked from `~/.lmstudio/models` is kept
when LM Studio is removed.
- Resetting app data, from Settings or the terminal interface, stops
before deleting anything if an engine PAIR installed cannot be removed,
and says which one.

### Bumps
- services: patch
- nvpair-cluster-manager: none
- nvpair-engine-manager: patch
- nvpair-errors: none
- nvpair-job-scheduler: none
- nvpair-manual-nodes: none
- nvpair-node-info: none
- nvpair-node-scanner: none
- nvpair-node-settings: none
- nvpair-proxy: none
- nvpair-tui: patch
- nvpair-ui-broker: none
- nvpair-workload-manager: none
<!-- /pair-release-intent:v1 -->

## Summary

Review fixes for the work below this in the stack. **Stacked on**
`fix/indeterminate-install-progress` (#145), at the top of #147 → #148 →
#117 →
#150 → #145; merge it after them, and before the stack ships.

### Engine removal (#150)

- **Junctions are never followed.** Since Go 1.23 a Windows junction is
reported
as an irregular file, not a symlink, so the symlink check let the
elevated
uninstaller read through one and empty the directory it names. Removal
now
descends only into what `Lstat` reports as a real directory. Two Windows
tests use real junctions; CI runs Go tests on Linux only, so they run on
  Windows machines.
- **The model store is matched by file identity.** A store that is a
symlink to
  another folder under the removal target was deleted, and case handling
followed the OS rather than the filesystem. `os.SameFile` replaces both.
New
tests cover a symlinked store, a store two levels down (the first to
exercise
  the recursion), and a store spelled in another case.
- `spec.md` now states what removing an engine may delete.

### Resets (#150)

The TUI and desktop resets wiped app data even when an engine could not
be
removed, deleting the only record that it was PAIR's. Both now stop
first and
say which engine, or that the service is not running. The orchestrator
gains its
first tests.

### Smaller fixes

- "Personal AI Router" → "NVIDIA PAIR" in the uninstall work's
user-visible
wording. The data path and firewall rule names keep the old name on
purpose.
- Catalog tests assert the error itself (#117).
- Getting started covers the update that renames the macOS app (#147).
- Engine lifecycle documents an LM Studio models folder moved inside
  `~/.lmstudio`.

## Test plan

- `go vet` (darwin, linux, windows) and `go test ./...` in
  `nvpair-engine-manager` and `nvpair-tui`.
- `npm run typecheck`, `npm run lint`, `npm run test:unit` from
`desktop/`.
- `TestUninstallTerminatesRunningInstance` fails locally on macOS
identically on
  `develop`. The Windows junction tests compile but did not run here.

---------

Signed-off-by: Terve <ntervalon@nvidia.com>
Noah-Tervalon-Nvidia pushed a commit that referenced this pull request Oct 9, 2026
…reenshots (#151)

## Description

A documentation pass over the release that landed in `develop` with
#147, #148, #117, #150, #145, and #153. It covers llama.cpp everywhere
the docs list engines, replaces the terminal-interface screenshots of
the old ten-tab interface, and corrects docs that no longer match the
code.

The branch was replayed onto `develop` after the stack was
squash-merged, so it carries only the docs commits.

<!-- pair-release-intent:v1 -->
### Changelog title
n/a

### Changelog body
n/a

### Bumps
- services: none
- nvpair-cluster-manager: none
- nvpair-engine-manager: none
- nvpair-errors: none
- nvpair-job-scheduler: none
- nvpair-manual-nodes: none
- nvpair-node-info: none
- nvpair-node-scanner: none
- nvpair-node-settings: none
- nvpair-proxy: none
- nvpair-tui: none
- nvpair-ui-broker: none
- nvpair-workload-manager: none
<!-- /pair-release-intent:v1 -->

## Scope

**Terminal interface screenshots**
(`docs/assets/onboarding/terminal-interface/`)

- The three existing images showed the ten-tab interface from before
#117. They are replaced with renders of the current Nodes, pairing, and
Jobs views, at the same size as the originals.
- New `04-tui-node-detail.png`: a machine's Engines and Models panes
with Ollama, LM Studio, and llama.cpp. It is placed in "Look at One
Machine".
- The renders drive the real `ui` views and styles with sample data for
two machines, so the layout, columns, and footer are the interface's own
output. The harness that produced them is not part of this pull request.

**Terminal interface capabilities**

- `docs/known-issues.mdx` and `docs/engine-lifecycle.mdx` said the
terminal interface cannot list or delete models, change ports, manage
another node's engines, or show which node ran a job. It does all of
these now. The lists keep only what is still true: updating an engine,
sending your own inference request, installing PAIR updates, and
surviving a dropped connection.
- `docs/engine-lifecycle.mdx`: ports and startup arguments can be edited
from a paired machine. Only uninstall, update, and CORS settings stay
local.
- `README.md`: leaving a cluster is `l` on the **Nodes** tab. There is
no Cluster tab any more.
- `docs/architecture.mdx`: the broker's `ping` is used by the
**Service** tab, not a health view.

**Terminal interface docs** (`docs/terminal-interface.mdx`,
`services/nvpair-tui/README.md`), checked against the code:

- It runs a broker and ten workers, not eleven.
- While the interface runs, its own logs go to the Logs tab, not stderr.
- The engine port, proxy port, and startup arguments are offered on a
peer and written through `engine:apply-settings`. Only restart and
uninstall are local-only. A peer's screen lists only its engine port,
though `p` still changes that peer's proxy port.
- Only a plain `go build` skips the update check. Builds made with
`services/build.sh` or `services/build.bat` check.
- `r` forgets a machine added with `f` without asking, and the "asks
first" rule says so. The catalog filter also matches tags and clears
with `c`.

**llama.cpp coverage**

- `README.md`: removed the claim that first-run setup does not preselect
llama.cpp. `WELCOME_ENGINE_DEFAULT_SELECTED` preselects it. The macOS
install step names **NVIDIA PAIR**.
- `docs/getting-started.mdx`:
- "Using an Engine's Own Command Line" gives llama.cpp's install path on
Windows, Linux, and macOS, its cache, and querying its router on `8081`.
The `OLLAMA_HOST` advice is marked as Ollama-only.
  - Ollama's path table gains a macOS row.
- The endpoint table gains a llama.cpp row: OpenAI-compatible paths on
`8080`, no Ollama `/api/...` and no Anthropic Messages API.
- `docs/troubleshooting.mdx`: a `llama-server` you started yourself on
`8080` hides requests from PAIR. The fix is to stop it and reopen PAIR,
because the proxy keeps its fallback port until the broker restarts.
- `docs/building.mdx` and `docs/engine-lifecycle.mdx`: model folders and
example ports include `~/.llamacpp`, `~/.lmstudio/models`, and `8080`.
- `services/installer/linux/INSTALL.md` and
`services/installer/macos/INSTALL.md`:
- Models live in each engine's own store, so deleting the bundle or
configuration leaves them.
- The bind-conflict note named `11435`, which is Ollama's relocated
engine port, not a proxy port. It now lists the three proxy ports.
- Linux: the bundle layout lists `nvpair-tui`, and the requirements say
`x86_64` or `arm64`, which `installer_build.sh` produces.
- Service and developer references that still assumed two engines:
  - the `nvpair-proxy` README and spec;
- the `nvpair-job-scheduler` spec, including the `scheduler:get-status`
shape;
  - the `nvpair-workload-manager` README and spec;
  - the `llamacpp-proxy` error IDs in `nvpair-errors`;
  - the `nvpair-ui-broker` README;
  - `.cursor/rules/proxy-inference-routing.mdc`;
  - `desktop/docs/services-backend.md`.
- `services/nvpair-engine-manager/README.md` lists
`engine:uninstall-managed`. `desktop/docs/services-parity.md` marks the
pull `percent` as optional, after #145.

**Not included**

- Desktop screenshots that still show only Ollama, such as
`getting-started/01-first-run-setup.png`. They need a capture from the
Electron app.
- `CHANGELOG.md`, which release-intent automation writes.

## Validation

- Swept every `*.md`, `*.mdx`, and `.cursor/rules/*.mdc` file for engine
lists that name Ollama or LM Studio without llama.cpp, and for
references to the old terminal interface. Each hit was checked against
the code. Statements that PAIR adopts an existing Ollama or LM Studio
are left alone, because llama.cpp is never adopted.
- Each factual change was checked against its source, including the
engine manifests, `engines.All()`, `schedule.go`, `engineproxy.go`,
`nodedetail.go`, `nodes.go`, `logoutput.go`, `updatecheck.go`,
`services/build.sh`, `installer_build.sh`, and `appdir.go`.
- A three-model review pass over the branch. Every finding was verified
against the code before it was applied.
- Reviewed each rendered screenshot against the doc text and alt text it
accompanies.
- `go vet ./ui/` in `services/nvpair-tui` after the harness was removed.
- `python3 scripts/release-intent/validate_pr.py --description-file
<this description> --skip-owned-files-check`

## Risk

Documentation and images only. No code, build, or versioned file
changes.

Found while checking and left for a separate change:

- In a colour terminal, an unplaced job's RAN ON cell renders as `c…`
instead of `choosing`. The styled text is truncated by its byte width,
and the tests render without colour, so they do not see it.
- A comment in `ui/updatecheck.go` still says "eleven workers".
- `nvpair-manual-nodes` probes hand-added nodes for Ollama and LM Studio
only, not llama.cpp.
- `installer_build.sh` does not copy `inference-dispatcher` into the
services tarball, so the Jobs tab's test traffic cannot start from it.

## Checklist

- [x] I have read the [Contributing
Guidelines](https://github.com/NVIDIA/Personal-AI-Router/blob/main/CONTRIBUTING.md).
- [x] Every commit is signed off (`git commit -s`), certifying the
[Developer Certificate of Origin](https://developercertificate.org/).
- [ ] New or existing tests cover the change. (Documentation only.)
- [x] Relevant documentation is updated.
- [x] I checked the diff, changed filenames, and commit messages for
credentials, private data, internal URLs, internal issue identifiers,
and generated artifacts.
- [x] I recorded the validation commands and results above.
- [x] I declared version bumps in the release-intent block above.
`services/versions.json` is written by automation — do not edit it by
hand.

---------

Signed-off-by: Chris Kelsey <ckelsey@nvidia.com>
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.

3 participants