Skip to content

feat(harbor): add the SWITCH tab - #285

Merged
T-Crypt merged 3 commits into
nextfrom
T-Crypt/harbor-switch-tab
Nov 1, 2026
Merged

T-Crypt merged 3 commits into
nextfrom
T-Crypt/harbor-switch-tab

Conversation

@T-Crypt

@T-Crypt T-Crypt commented Nov 1, 2026

Copy link
Copy Markdown
Owner

Adds the SWITCH tab to the harbor, step 5 of the switchboard plan. From a phone, it shows who holds each card and lets a paired device switch the card to an engine or stop the engine that holds it.

What a user sees

  • Each card with its state, its memory, its holders and a CORDONED mark.
  • Every engine in berth.toml with its state and message, and its models with a dot for a loaded one.
  • On a machine with several cards, a picker (ALL, GPU0, GPU1, ...) that narrows the engines to one card and is remembered.
  • On a paired device: SWITCH on an engine that is not up, STOP on the engine that holds the card, PINNED (no STOP) for a pinned holder, and PAIR A PHONE, which shows a QR code for another phone.
  • On any other device: the same board, read only, with a line that says to run berth pair.

Keys 1 to 4 now open CARD, SWITCH, ENGINES and ABOUT, in the order the tabs show. Before this, 2 opened ENGINES and 3 opened ABOUT.

How it works

The harbor has no start or stop logic of its own. GET /v1/board forwards to the gateway's GET /v1/berth/board. POST /v1/board/switch and /v1/board/stop forward to POST /v1/berth/switch and /v1/berth/stop. The harbor sends the node's API key with each. The harbor reads the key with the new apikey.Load and never creates it. berth harbor --gateway and --key-file override where the gateway and the key are.

Reading the board needs no pairing. Each action and POST /v1/pair/code refuses, in this order: a cross-site request (403), a request without X-Berth: 1 (403), and a device that is not paired (401). A body other than {"engine":"NAME"} gets 400. Only the engine name goes on to the gateway. A missing key gives 503, and the tab says how to fix it. The guard still refuses every other non-GET request.

Load for a llama-swap model is step 6 of the plan, and the /gui/ index redirect is step 7. Neither is in this PR.

Proof

  • internal/harbor/switchboard_test.go runs against a real berthd with a pairing file and a fake gateway. It checks that an unpaired read reaches the gateway with the key, and that each gate gives its status and code without calling the gateway. It also checks that a paired switch forwards only {"engine":"strata"} with the bearer key, that a bearer token works as the cookie does, that a 409 from the gateway reaches the page unchanged, that the pair code gives an https link and a QR data URL, and that a missing key gives 503. I removed each gate in turn (cross-site, X-Berth, pairing, empty engine, body reshaping, bearer header, missing key, https link), and each removal failed a test. I deleted one test that passed even without the guard it was meant to cover.
  • In Chromium (Playwright) at 390 px and at 1440 px, against this branch's own harbor with its strict CSP and ?demo, 42 checks passed. They cover the cards, the cordon, the picker and its memory across a reload, which buttons each state gets, the pinned holder, the model list, 44 px buttons, no sideways scroll on the phone, the new key order, the no-key message, and no console errors.
  • On aphotic, a harbor built from this branch on a spare loopback port read the live gateway's board with the real key. It showed gpu0 held by Strata and all five engines, with no buttons because that browser was not paired. An unpaired stop got 401, and Strata kept its PID.
  • go build, gofmt, go vet, go test ./..., the Windows build and vet, and golangci-lint (0 issues) pass locally.

Still to do: the plan's last check, a switch from a paired iPhone over the tailnet, after this build runs on aphotic.

@T-Crypt
T-Crypt merged commit 0ec8961 into next Nov 1, 2026
4 checks passed
@T-Crypt
T-Crypt deleted the T-Crypt/harbor-switch-tab branch November 1, 2026 19:58
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.

1 participant