finish the rebrand: the parts a user reads, and the manifest that was wrong
The extraction landed months ago and the platform side is clean. What was left was inside the plugin, and it was the half that shows. ── Names a user actually reads ── The three registered panels were still called "Headscale servers", "Headscale" and "Headscale" in the panel picker, and the server picker rendered "Headscale" as its own heading. Those name OUR panels, not the upstream server, so they were simply missed. Deliberately NOT a find-and-replace. Most of the ~175 "headscale" strings in here are correct — "no active Headscale server", "headscale timed out", "is this a Headscale server?" all refer to the thing this plugin drives, and renaming them would make the errors lie. What was fixed is the plugin's own identity, plus nine stale `/headscale` route paths that are wrong rather than merely old (the route is `/offscale`) and one reference to `queries/headscale.ts`, a file the extraction deleted. The console's pty session key moves from `headscale-console-<id>` to `offscale-console-<id>`. That one has a real cost: a console open at the moment of the change keeps running in the sidecar under the old key with nothing pointing at it, until that sidecar restarts. Paid once, deliberately, rather than carrying the pre-extraction name in persisted state forever. ── The manifest was missing a dependency it has always had ── `sidecar/ssh.ts` spawns `ssh`, and so does the reachability probe. The manifest said "Offscale needed nothing", so install checked for nothing and a host without an ssh client failed at RUNTIME with a spawn error — precisely the failure `osDependencies` exists to turn into a refusal at install time. PLUGIN.md listed ssh as a host dependency all along; the manifest was the file that was wrong. Missed because ssh is on effectively every Linux box, which is also why declaring it costs nothing there and is the whole difference where it is absent. ── Documentation that said the opposite of the code ── PLUGIN.md instructed the reader to carry `assist.ts` in "but do not wire it up", as a marker. It has been wired for some time: `policy.ts` routes `POST /_officer/policy/assist`, `PolicyView.tsx` renders `<PolicyAssistant />`, and the manifest declares the route in `readOnlyWrites`. Corrected rather than deleted, because that instruction is exactly the kind someone would follow — a person tidying "unused" code would have found it wired and assumed the document knew something they did not. Present-tense identifiers corrected to the shipped names; the "before" columns of the migration tables still say headscale, because that is what they record. A note at the top says which is which. tsgo clean, 21 plugin tests pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -109,7 +109,7 @@ adding core routes forever without breaking installs.
|
||||
## The database
|
||||
|
||||
**Tables live in `public`, prefixed with the app-name** — `offscale_servers`, exactly as the codebase
|
||||
already does (`headscale_servers`, `music_favorites`, `vault_tokens`). No new machinery.
|
||||
already does (`music_favorites`, `vault_tokens`). No new machinery.
|
||||
|
||||
### A Postgres schema per plugin was tested and rejected
|
||||
|
||||
@@ -255,7 +255,7 @@ use is read for most roles. Say Developers get `read` and nobody gets `write`.
|
||||
Two implementation consequences, both inside the plugin:
|
||||
|
||||
1. **The queries stop scoping by the caller.** Every one takes the caller's `userId` today —
|
||||
`listHeadscaleServers(userId)`, `getActiveHeadscaleCredentials(userId)` — and the schema is per-user
|
||||
`listOffscaleServers(userId)`, `getActiveOffscaleCredentials(userId)` — and the schema is per-user
|
||||
because of it. Under this model a member sees the **owner's** rows, so those resolve to the owner's id
|
||||
always. The per-user shape stays in the table, unused, and becomes the seam if isolation is ever wanted.
|
||||
|
||||
@@ -399,7 +399,7 @@ Read end to end on 2026-08-14. This is what has to move.
|
||||
|
||||
### Backend — 2,406 lines
|
||||
|
||||
`/api/headscale` is **18 lines**: a pure `createSidecarProxy`, no Headscale knowledge, "must never grow app
|
||||
`/api/offscale` is **18 lines**: a pure `createSidecarProxy`, no Headscale knowledge, "must never grow app
|
||||
logic". Everything is in the sidecar under `/_officer/*`, dispatched by `routes.ts` to eight handlers —
|
||||
`servers · nodes · users · keys · policy · enroll · ssh-test · companion`.
|
||||
|
||||
@@ -422,9 +422,9 @@ Host dependencies: `officerdb` (db + crypto), `DATA_PATH`, `officer-url.mjs`, `c
|
||||
|
||||
### Frontend — 29 files, 27 endpoints
|
||||
|
||||
Three registered panels (`headscale-servers`, `headscale-nav`, `headscale-view`, all
|
||||
`availableOnPanel: false`) inside a locked `WorkspaceView`, with `headscale-view` dispatching on
|
||||
`useHeadscaleSection()` to eight section views: Servers · Nodes · Users · Keys · Invites · Policy ·
|
||||
Three registered panels (`offscale-servers`, `offscale-nav`, `offscale-view`, all
|
||||
`availableOnPanel: false`) inside a locked `WorkspaceView`, with `offscale-view` dispatching on
|
||||
`useOffscaleSection()` to eight section views: Servers · Nodes · Users · Keys · Invites · Policy ·
|
||||
Diagnostics · Console.
|
||||
|
||||
It **follows the navigation conventions** — no `usePanelChannel` anywhere, no opaque clicks, the section
|
||||
@@ -442,11 +442,25 @@ The whole frontend↔host coupling, which becomes the plugin API:
|
||||
| `state/useDashboardState` | per-user layout, backed by `/api/dashboards`, a `core` permission — stays host-provided |
|
||||
| `../Terminal/Terminal` → `TerminalView` | **the awkward one** — a code dependency on another panel app |
|
||||
|
||||
### `assist.ts` travels, but stays unwired
|
||||
> **On names.** This document was written before the rebrand and describes the extraction as it was
|
||||
> planned. Present-tense claims about identifiers were corrected to the shipped names on 2026-08-16;
|
||||
> the "before" columns of the migration tables still say `headscale` on purpose, because that is what
|
||||
> they are recording. Where this file and the code disagree, the code is right.
|
||||
|
||||
The ACL-drafting assistant was written and never tested. **Carry it into the plugin, do not delete it, and
|
||||
do not wire it up** — it is there as a marker that the idea exists, to be finished or removed deliberately
|
||||
later. Do not tidy it away as unused code.
|
||||
### `assist.ts` — WIRED, and this section used to say the opposite
|
||||
|
||||
This said "carry it into the plugin, do not wire it up" and told the reader to leave it as a marker. That
|
||||
has not been true for some time: `sidecar/policy.ts:5` imports `handlePolicyAssistRoute` and routes
|
||||
`POST /_officer/policy/assist`, `web/PolicyView.tsx:158` renders `<PolicyAssistant />`, and the manifest
|
||||
lists `/policy/assist` in `readOnlyWrites` — which is a declaration that the route exists and is a read.
|
||||
|
||||
Corrected 2026-08-16 rather than deleted, because the instruction that was here is exactly the kind a
|
||||
future reader would have followed: someone tidying "unused" code would have found it wired and assumed the
|
||||
document knew something they did not. A doc that confidently describes the opposite of the code is worse
|
||||
than no doc.
|
||||
|
||||
What is still true: it drafts, it never writes. The route returns a proposed policy and the owner saves it
|
||||
through the normal `PUT /_officer/policy` path or not at all.
|
||||
|
||||
---
|
||||
|
||||
@@ -510,7 +524,7 @@ layouts rather than one application. Non-compliance is not refused so much as **
|
||||
is nowhere to put a screen.
|
||||
|
||||
The shell registers the pair `<prefix>` and `<prefix>/:section`, exactly as the core screens do
|
||||
(`/headscale/:section`), so a plugin's sections stay addressable, linkable and cmd-clickable. Panels read
|
||||
(`/offscale/:section`), so a plugin's sections stay addressable, linkable and cmd-clickable. Panels read
|
||||
`useParams` independently — nothing is passed between them, so they cannot disagree. `appTypes.allowed`
|
||||
is pinned to that plugin's own panel keys, so a persisted layout naming something else falls back rather
|
||||
than rendering another plugin's panel inside this one.
|
||||
|
||||
Reference in New Issue
Block a user