6 Commits
Author SHA1 Message Date
pastilhasandClaude Opus 5 66383aa9a4 the lists re-read when the agent changes something
an agent deleted two proxy hosts and both stayed on screen. the person had the
answer in the transcript and a stale list six inches above it — which is the
one thing a chat beside a list has to get right, and the reason it is beside
the list at all.

so the chat panel bumps the refresh channel when a turn ran tools, and every
section re-reads. gated on hadToolCalls: a turn that only talked changed
nothing, and re-reading after every sentence would put six requests behind
every reply.

the channel is renamed with it. it was proxy-manager:connection-changed, named
after its only publisher; it has two now and the second is the bigger one, so
it says what it MEANS — something at NPM changed, read it again.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043
2026-08-25 19:03:35 +00:00
pastilhasandClaude Opus 5 417e091dfe a nav down the left, whatever it selects beside it
two fixed panels could show two of NPM's seven sections and had nowhere to put
the other five. now the left column is the section list — proxy hosts,
redirections, streams, 404 hosts, access lists, certificates, connection — and
the right renders whichever is chosen.

NPM's own vocabulary, in NPM's own order. somebody who has used it should find
the same words, and somebody who has not will end up in NPM's UI eventually
anyway; better names would just mean learning both. "404 hosts" reads oddly and
is theirs, so the hint under it says what it means.

the two panels never speak. the nav writes ?section= and the detail pane reads
it — navigation-audit's rule, and it is what makes a section linkable and
survive a reload. changing section clears ?host=, which belonged to the list
that was open.

three things folded up while doing it. the unconfigured check moved to the
detail pane and happens once, so all seven sections say "no connection yet" the
same way instead of six saying it badly. the settings gear is gone: connection
is a nav row now. and the four list-only sections are one component, differing
only in what a row says — proxy hosts and certificates stay separate because
they have real behaviour, and folding them in would be a props bag that is a
worse version of both.

the counts in the nav are one request each. affordable against a local NPM, and
they answer "is this worth opening" without a click. a count that fails is
absent rather than zero — zero is a fact about NPM, a failure is a fact about
the request, and the detail pane reports that properly.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043
2026-08-25 19:00:24 +00:00
pastilhasandClaude Opus 5 3d02cb6229 saving a connection now tells the other panels
reported on the first successful save: everything still showed no data and the
form stayed open, and a manual page refresh was the only way through.

two faults, one report. the form did not close itself — nothing cleared
?config, so a save left the settings view sitting on top of a plugin that now
worked. and the two reader panels are SIBLINGS in a workspace layout: they are
mounted independently, neither owns the other, and ConnectionPanel cannot call
into them. so they went on showing the failure they had loaded back when no
connection existed.

a channel is the sanctioned answer for exactly this — docs/navigation-audit.md
puts addressable state in the URL and leaves channels for "something changed,
re-read it". nobody wants to link to the moment after a save.

the nonce is a plain module counter, copied deliberately from officerdev's
channels.ts along with the reason: setSignal(n => n + 1) applies against the
value captured at render, so two bumps in one render window both compute
snapshot + 1 and the second overwrites the first — and nobody re-reads.
Date.now() is the same bug at millisecond scale.

disconnect bumps it too. the panels must stop showing hosts they can no longer
reach.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043
2026-08-25 16:58:52 +00:00
pastilhasandClaude Opus 5 83a8558e60 do not print a proxy's error page at somebody
first real test of the settings form returned openresty's 502 Bad Gateway
document, markup and all, into the panel. installing this plugin restarts
officer, and a request made inside that window reaches nginx proxy manager,
finds nothing behind it, and gets a web page back. useClient throws the
response body as the message, so the panel printed it.

worth keeping the irony: this plugin manages the proxy whose error page it
could not read.

the check is the body's SHAPE, not the status, because there is no status that
separates them — api/router.ts uses 502 for "NPM answered and refused", which
is a real answer we must not swallow. ours arrives as a sentence, theirs as a
document. gateway status AND html means the hop in front of officer answered;
html on its own still is not a sentence and says so without the tags.

all three panels go through it now.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043
2026-08-25 16:55:55 +00:00
pastilhasandClaude Opus 5 ee8edd3709 the settings form, and the branch that never ran
the plugin could not be configured. /_config had GET, PUT, DELETE and a test
that validates without storing, and nothing rendered any of it — the
connection had to be inserted by hand.

ConnectionPanel is both the wizard and the settings page, one component, the
same choice jellyfin's ConnectionSection makes and for the same reason: a
connection edited months later goes through exactly the path that stored the
first one. HostsPanel shows it when nothing is configured and on ?config=1,
which is in the URL so it survives a refresh. test comes before save and
stores nothing, so a wrong password cannot replace a working one. the secret
field starts empty even when one is stored, and empty means keep — that is
what lets a typo in the URL be fixed without going to find the password.

and while wiring the unconfigured case, it turned out that case never fired.

both panels read `(error as { body?: Failure }).body` to decide whether a
failure was unconfigured, unauthenticated or unreachable. useClient throws
ApiError(status, message) and has no `.body` at all, so that was always
undefined: every failure fell through to the generic message, and the careful
explanation of NPM's 404-means-credential quirk was written, shipped and
unreachable. asFailure now reads the STATUS, which the router already chooses
deliberately — 409 nothing stored, 503 no answer, 502 answered and refused.

owner-only is explained rather than reported. a member hitting 403 here reads
why: it is an admin login and NPM has no read-only role.

still not installed, still no db:push.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043
2026-08-25 16:51:31 +00:00
pastilhasandClaude Fable 5 5914e96cf3 proxy-manager: nginx proxy manager in officer
reads hosts and certificates against a live NPM 2.15.1. one grantable
permission, with /_config owner-only — reading which hosts are healthy
is worth sharing, an NPM admin login is not, and NPM has no read-only
role.

the quirk that shapes the whole plugin: NPM answers an unauthenticated
read with 404, not 401, because its router only registers a resource
once a token is present. so 'not signed in' and 'no such endpoint' are
the same response, and a naive client tells you a feature is missing
when your password is wrong. the client mints a fresh token and retries
once, then reports a persistent 404 as likelyAuth.

certificates sort by expiry rather than name, which is the only ordering
that answers a question.

no create/edit/delete and no cert issuance: NPM's own UI does those and
a wrong proxy host takes a domain off the internet.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 08:38:15 +00:00